并发包之读写锁
并发包之读写锁
之前的锁基本都是排他锁,这些锁在同一时刻只允许一个线程进行访问,而读写锁在同一时刻可以允许多个读线程访问,但是在写线程访问时,所有的读线程和其他写线程均被阻塞,读写锁维护了一对锁,一个读锁和一个写锁,通过分离读锁和写锁,使得并发性相比一般的排它锁有了很大的提升。
没有读写锁的时候,要完成读写功能就要使用Java的等待通知机制,就是当写操作开始时,所有晚于写操作的读操作均会进入等待状态,只有写操作完成并进行通知后,所有等待的读操作才能继续进行,这样的目的是不会出现脏读。改用读写锁实现上述功能,只需要在读操作时获取读锁,写操作时获取写锁即可。当写锁被获取到后,后续的读写操作都会被阻塞,写锁释放以后,读写操作继续执行,相对于等待通知机制,更加简单明了。
在读大于写的情况下,读写锁能够提供比排他锁更好的并发性和吞吐量。并发包中读写锁的实现是ReentrantReadWriteLock,支持公平性选择,重入,锁降级(遵循获取写锁,获取读锁再释放写锁的次序,写锁能够降级成为读锁。)
1 | 读写锁的例子 |
ReadWriteLock管理一组锁,一个是只读的锁,一个是写锁,读锁可以在没有写锁的时候被多个线程同时持有,写锁是独占的
一个获得了读锁的线程必须能看到前一个释放的写锁所更新的内容。
读写锁比互斥锁允许对于共享数据更大程度的并发,每次只能有一个写线程,但是同时可以有多个线程并发地读数据,ReadWriteLock适用于读多写少的并发情况。
ReadWriteLock是一个接口,主要有两个方法
1 | public interface ReadWriteLock |
源码分析
1 | public ReentrantReadWriteLock() { |
默认构造方法时非公平模式,创建的Sync是NonfairSync对象,然后初始化读锁和写锁。
Sync 继承了AbstractQueuedSynchronizer,而Sync 是一个抽象类,NonfairSync 和FairSync 继承了Sync, 并重写了其中的抽象方法。
Sync 分析
Sync中提供了很多方法,但是有两个方法是抽象的,子类必须实现。下面以FairSync为例,分析一下这两个抽象方法:
1 | static final class FairSync extends Sync { |
writerShouldBlock和readerShouldBlock方法都表示当有别的线程也在尝试获取锁时,是否应该阻塞。
对于公平模式,hasQueuedPredecessors()方法表示前面是否有等待线程。一旦前面有等待线程,那么为了遵循公平,当前线程也就应该被挂起。
1 | static final class NonfairSync extends Sync { |
从上面可以看到,非公平模式下,writerShouldBlock直接返回false,说明不需要阻塞;而readShouldBlock调用了apparentFirstQueuedIsExcluisve()方法。该方法在当前线程是写锁占用的线程时,返回true;否则返回false。也就说明,如果当前有一个写线程正在写,那么该读线程应该阻塞。
继承AQS的类都需要使用state变量代表某种资源,ReentrantReadWriteLock中的state代表了读锁的数量和写锁的持有与否,整个结构如下:
可以看到state的高16位代表读锁的个数;低16位代表写锁的状态。
Author: corn1ng
Link: https://corn1ng.github.io/2018/01/19/Java并发/并发包之读写锁/
License: 知识共享署名-非商业性使用 4.0 国际许可协议