ReadWriteLock维护了一对相关的锁,分别是只读锁、写锁。只读锁可以被多个线程同时持有,写锁是独占的。
ReentrantReadWriteLock实现了ReadWriteLock接口,支持类似ReentrantLock功能。读写锁会更加体现公平性问题,比如读锁和写锁的获取顺序。实现类中也提供了两种模式,公平和非公平模式,默认为非公平的。
接下来我们看下相关源码,来分析下ReentrantReadWriteLock的实现原理,以及提供的两种方式的区别。
我们首先看下写锁的获取,下面几个公共方法是作为判定当前持有锁的类型:
1 | static final int SHARED_SHIFT = 16; |
获取写锁流程:
1 | public final void acquire(int arg) { |
写锁的获取流程同ReentrantLock
获取锁相同,子类通过实现tryAcquire
来实现具体的流程,我们看下代码:
如果当前没有线程获取锁,也就是state = 0,此时会直接通过CAS设置state的值,如果成功则返回,获取到了锁。如果当前其他线程获取到锁,
- 持有的是读锁 w = 0,则会调用
acquireQueued
方法,上节我们分析过了会进入队列并阻塞。 - 持有的事写锁,当前线程非独占线程,同样也是调用
acquireQueued
方法。 - 独占为当前线程,表示重入该锁。
- 获取锁的数量是有上限的,也是就是上面
MAX_COUNT
,如果请求锁的数量大于该值,则会抛出异常
我们先看下一个线程是如何获取读锁的。
1 | public final void acquireShared(int arg) { |
当获取读锁时,如果其他线程持有写锁,并且不是当前线程,则直接进入doAcquireShared
流程,会在1处返回。
在持有写锁的情况下,支持锁的降级,同一个线程获取写锁的情况下,可以再次获取读锁,来实现降级。进入3内的代码。
同时多个线程获取读锁是不需要阻塞的,可以同时获取读锁。不过在竞争条件下获取读锁,会进入步骤4来获取读锁。
我们根据上面在获取写锁的情况下,如果不是当前线程持有锁,则会进入doAcquireShared
,会进入队列,并且阻塞当前线程。
1 | private void doAcquireShared(int arg) { |
当写锁调用unlock
释放后,后续等待的读锁的线程会依次释放,直到下一个获取写锁Node。