ReadWriteLock 接口维护了一对相关的锁
,一个用于只读操作,另一个用于写入操作。只要没有 writer,读取锁
可以由多个 reader 线程同时保持。写入锁
是独占的。
所有 ReadWriteLock 实现都必须保证 writeLock 操作的内存同步效果也要保持与相关 readLock 的联系。也就是说,成功获取读锁的线程会看到写入锁之前版本所做的所有更新。
与互斥锁相比,读-写锁允许对共享数据进行更高级别的并发访问。虽然一次只有一个线程(writer 线程)可以修改共享数据,但在许多情况下,任何数量的线程可以同时读取共享数据(reader 线程),读-写锁利用了这一点。从理论上讲,与互斥锁相比,使用读-写锁所允许的并发性增强将带来更大的性能提高。在实践中,只有在多处理器上并且只在访问模式适用于共享数据时,才能完全实现并发性增强。
与互斥锁相比,使用读-写锁能否提升性能则取决于读写操作期间读取数据相对于修改数据的频率,以及数据的争用——即在同一时间试图对该数据执行读取或写入操作的线程数。例如,某个最初用数据填充并且之后不经常对其进行修改的 collection,因为经常对其进行搜索(比如搜索某种目录),所以这样的 collection 是使用读-写锁的理想候选者。但是,如果数据更新变得频繁,数据在大部分时间都被独占锁,这时,就算存在并发性增强,也是微不足道的。更进一步地说,如果读取操作所用时间太短,则读-写锁实现(它本身就比互斥锁复杂)的开销将成为主要的执行成本,在许多读-写锁实现仍然通过一小段代码将所有线程序列化时更是如此。
接口中定义的方法:
- Lock readLock() 返回用于读取操作的锁
- Lock writeLock() 返回用于写入操作的锁
接口的实现类ReentrantReadWriteLock:
构造方法:
- ReentrantReadWriteLock() 使用默认(非公平)的排序属性创建一个新的 ReentrantReadWriteLock
-
当非公平地(默认)构造时,未指定进入读写锁的顺序,受到 reentrancy 约束的限制。连续竞争的非公平锁可能无限期地推迟一个或多个 reader 或 writer 线程,但吞吐量通常要高于公平锁。
- ReentrantReadWriteLock(boolean fair) 使用给定的公平策略创建一个新的 ReentrantReadWriteLock
-
当公平地构造线程时,线程利用一个近似到达顺序的策略来争夺进入。当释放当前保持的锁时,可以为等待时间最长的单个 writer 线程分配写入锁,如果有一组等待时间大于所有正在等待的 writer 线程 的 reader 线程,将为该组分配写入锁。
读锁、写锁:
ReentrantReadWriteLock内定义了两个嵌套的类:static class ReentrantReadWriteLock.ReadLock (读锁)、 static class ReentrantReadWriteLock.WriteLock(写锁),分别通过成员方法readLock()、WriteLock()获取类实例。
public static class ReentrantReadWriteLock.WriteLock extends Object implements Lock, Serializable
读锁和写锁都是Lock接口的实现类,而不是ReadWriteLock的实现类。ReadLock和WriteLock都有newCondition()方法,但ReadLock不支持条件,调用newConditin()方法会抛出异常 UnsupportedOperationException。WriteLock的newCondition()方法可以正常使用,且和ReentrantLock中的newCondition()使用相同。
锁降级:
- 锁降级:从写锁变成读锁;
- 锁升级:从读锁变成写锁
ReentrantReadWriteLock支持锁降级,不支持锁升级
测试代码产生死锁,因为同一个线程中,在没有释放读锁的情况下,就去申请写锁,这属于锁升级,ReentrantReadWriteLock是不支持的。
我们交换一下读写锁的获取顺序:
代码正常结束,同一个线程中,在没有释放写锁的情况下,也获取到了读锁,这属于锁降级,ReentrantReadWriteLock支持锁降级。
ReetrantReadWriteLock读写锁互斥关系:
- 读锁与读锁:读锁与读锁之间不互斥,同时可以有多个线程并发地读数据。
- 读锁与写锁:读锁与写锁之间互斥。
- 写锁与写锁:写锁与写锁之间互斥。
public class TryLockTest {
static ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
static ReadLock readLock = rwLock.readLock();
static WriteLock writeLock = rwLock.writeLock();
public static void main(String[] args) {
new Thread(new Runnable(){
@Override
public void run() {
m3();
}
}).start();
new Thread(new Runnable(){
@Override
public void run() {
m1();
}
}).start();
}
public static void m1(){
readLock.lock();
for(int i=0 ;i<20 ; i++){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("m1 *running");
}
readLock.unlock();
}
public static void m2(){
readLock.lock();
for(int i=0 ;i<20 ; i++){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("m2 **running");
}
readLock.unlock();
}
public static void m3(){
writeLock.lock();
for(int i=0 ;i<20 ; i++){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("m3 ***running");
}
writeLock.unlock();
}
public static void m4(){
writeLock.lock();
for(int i=0 ;i<20 ; i++){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("m4 ***running");
}
writeLock.unlock();
}
}
在main方法中分别调用m1()+m2(),m1()+m3(),m3()+m4()可以看到控制台的输出效果:
从输出结果可以看出,读锁与读锁不互斥,读锁与写锁互斥,写锁与写锁互斥。
总结:
1.Java并发库中ReetrantReadWriteLock实现了ReadWriteLock接口并添加了可重入的特性
2.ReetrantReadWriteLock读写锁的效率高于synchronized关键字
3.ReetrantReadWriteLock读写锁的实现中,读锁使用共享模式;写锁使用独占模式,即读锁可以在没有写锁的时候被多个线程同时持有,写锁是独占的
4.ReetrantReadWriteLock读写锁的实现中,需要注意的,当有读锁时,写锁就不能获得;而当有写锁时,除了获得写锁的这个线程可以获得读锁外,其他线程不能获得读锁