章
目
录
前面讲到的 synchronized 内部锁和 ReentrantLock 都是独占锁(排他锁),同一时间只允许一个线程执行同步代码块,可以保证线程的安全性,但是执行效率低。
ReentrantLock是一种排他锁,同一时刻只允许一个线程访问,ReadWriteLock 接口的实现类 ReentrantReadWriteLock 读写锁提供了两个方法:readLock()和writeLock()用来获取读锁和写锁,也就是说将文件的读写操作分开,分成2个锁来分配给线程,从而使得多个线程可以同时进行读操作。
读写锁是什么
读写锁(Readers-Writer Lock)顾名思义是一把锁分为两部分:读锁和写锁,其中读锁允许多个线程同时获得,因为读操作本身是线程安全的,而写锁则是互斥锁,不允许多个线程同时获得写锁,并且写操作和读操作也是互斥的。
总结来说,读写锁的特点是:读读共享、读写互斥、写写互斥。
ReadWriteLock接口
public interface ReadWriteLock {
// 读锁
Lock readLock();
// 写锁
Lock writeLock();
}
读写锁维护了两个锁,一个是读操作相关的锁也称为共享锁,一个是写操作相关的锁也称为排他锁。通过分离读锁和写锁,其并发性比一般排他锁有了很大提升。
ReentrantReadWriteLock 读写锁
ReentrantReadWriteLock 读写锁实现了ReadWriteLock 接口,我们先看下基本使用:
基本使用
//定义读写锁
ReadWriteLock rwLock = new ReentrantReadWriteLock();
//获得读锁
Lock readLock = rwLock.readLock();
//获得写锁
Lock writeLock = rwLock.writeLock();
//读数据
readLock.lock();
try {
//读数据
} finally {
readLock.unlock();
}
//写数据
try {
writeLock.lock();
//写数据
} finally {
writeLock.unlock();
}
读读共享
从下面这段代码的运行状态中可以看出,创建的5个线程都同时拿到了读锁,而不用每隔3秒拿一个。说明读锁是共享的。
/**
* ReadWriteLock读写锁允许多个线程同时读,即读读共享
*/
public class Test01 {
static class Service {
//定义读写锁
ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
public void read() {
//定义方法读取数据
readWriteLock.readLock().lock(); //申请读锁
try {
System.out.println(Thread.currentThread().getName() + "获得读锁");
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
readWriteLock.readLock().unlock(); //释放读锁
}
}
}
public static void main(String[] args) {
Service service = new Service();
for(int i = 0; i < 5; i++) {
new Thread(new Runnable() {
@Override
public void run() {
service.read();
}
}).start();
}
}
}
写写互斥
从下面的例子中可以看出来,写写是互斥的,每个线程必须依次获得写锁:
public class Test02 {
static class Service {
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
public void write() {
readWriteLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "获得了写锁");
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
readWriteLock.writeLock().unlock();
}
}
}
public static void main(String[] args) {
Service service = new Service();
for(int i = 0; i < 5; i++) {
new Thread(new Runnable() {
@Override
public void run() {
service.write();
}
}).start();
}
}
}
读写互斥
一个线程获得读锁,写线程等待。一个线程获得写锁,其他线程等待。
public class Test03 {
static class Service {
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private Lock readLock = readWriteLock.readLock();
private Lock writeLock = readWriteLock.writeLock();
public void read() {
readLock.lock();
try {
System.out.println(Thread.currentThread().getName() + "读取数据:" + System.currentTimeMillis());
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
readLock.unlock();
}
}
public void write() {
writeLock.lock();
try {
System.out.println(Thread.currentThread().getName() + "写数据:" + System.currentTimeMillis());
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
writeLock.unlock();
}
}
}
public static void main(String[] args) throws InterruptedException {
Service service = new Service();
new Thread(new Runnable() {
@Override
public void run() {
service.read();
}
}).start();
for(int i = 0; i < 5; i++) {
new Thread(new Runnable() {
@Override
public void run() {
service.write();
}
}).start();
TimeUnit.MILLISECONDS.sleep(500);
}
for(int i = 0; i < 5; i++) {
new Thread(new Runnable() {
@Override
public void run() {
service.read();
}
}).start();
}
}
}
总结
多个读锁之间不互斥,读锁与写锁互斥,写锁与写锁互斥(只要出现写操作的过程就是互斥的)。在没有线程进行写操作时,进行读取操作的多个线程都可以获取读锁,而进行写入操作的线程只有在获取写锁后才能进行写操作。即多个线程可以同时进行读取操作,但是同一时刻只允许一个线程进行写入操作。