欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java 线程并发 ReadWriteLock 应用场景

程序员文章站 2022-03-05 17:03:18
...
问:谈谈 ReadWriteLock 的应用场景?

答:首先 Lock 相对于 synchronized 来说更加面向对象,多个线程想要执行同步互斥就必须使用同一把 Lock 对象锁。而 ReadWriteLock(实现类 ReentrantReadWriteLock)读写锁提供了读锁和写锁接口,多个读锁不互斥,读锁与写锁互斥,多个写锁互斥,这些特性都是 JVM 控制的。

而多线程并发场景中对同一份数据进行读写操作会涉及到线程并发安全问题,也就是说会出现一个线程读数据的时候另一个线程在写数据的情况,或者一个线程在写数据的时候另一个线程也在写的情况,这样就会导致数据的不一致性。

而并发读写操作保证安全的常规解决办法是在读写操作上加入互斥锁,这种情况下并发读写效率会打折扣,因为大多数情况下我们对同一数据读操作的频率会高于写操作,而线程与线程间的并发读操作是不涉及并发安全问题的,所以没有必要给读操作加互斥锁,只要保证读写、写写并发操作上锁是互斥的就行。

由此读写锁就诞生了,ReentrantReadWriteLock 从名字上就可以看出来是可重入的读写锁,其允许多个线程获得 ReadLock,但只允许一个线程获得 WriteLock,所以读写锁的特点就是并发读读不互斥、并发读写互斥、并发写写互斥。

ReentrantReadWriteLock 内部有一个读锁和一个写锁,线程进入读锁的前提条件是没有其他线程占用写请求或者有写请求但调用线程和持有锁的线程是同一个线程;而线程进入写锁的前提条件是没有其他线程占用读锁,也没有其他线程占用写锁。这些特性可能解释的比较难懂,那就来个实例说下。

ReadWriteLock rtLock = new ReentrantReadWriteLock();
rtLock.writeLock().lock();
System.out.println("writeLock");
rtLock.readLock().lock();
System.out.println("readLock");

上面这段代码属于锁降级,不会导致死锁,但没有正确的释放锁,从写锁降级成读锁不会自动释放当前线程获取的写锁,仍然需要主动的释放,否则别的线程永远也获取不到写锁。正确做法应该如下所示:

ReadWriteLock rtLock = new ReentrantReadWriteLock();
rtLock.writeLock().lock();
System.out.println("writeLock");
rtLock.readLock().lock();
System.out.println("readLock");
rtLock.writeLock().unlock();
System.out.println("unlock writeLock");

ReadWriteLock rtLock = new ReentrantReadWriteLock();
rtLock.readLock().lock();
System.out.println("readLock");
rtLock.writeLock().lock();
System.out.println("writeLock");

上面这段代码属于锁升级,会产生死锁,因为同一个线程在没有释放读锁的情况下就去申请写锁是不成立的,读写和写写是互斥的。

可以看到 ReentrantReadWriteLock 不支持锁升级,只支持锁降级。锁降级的意思就是对同一线程从写锁变成读锁,锁升级的意思就是对同一线程从读锁变成写锁。而读锁是多线程共享锁,写锁是多线程互斥锁,所以写锁的并发限制比读锁高,故升降就是从这个特性来的。

问:为什么可以锁降级,也就是说,为什么释放写锁之前可以获取读锁?

答:你既然拿到写锁了,其他线程就没法拿到读锁或者写锁,你再(在拿到写锁的线程中)拿读锁,其实不会和其他线程的写锁发送冲突的,因为你拿到写锁到写锁释放的这段时间,其他线程是无法拿到任何锁的。

问:为什么不可以锁升级,即为什么获取读锁之后不能再获取写锁?

答:锁升级就没法做到读写互斥了。两个线程都拿到了读锁,前一个线程升级成写锁,后一个线程的读锁又没释放,所以就没法做到读写互斥了。

本文参考自 线程并发 ReadWriteLock 应用场景相关问题解析