总体见:详解 读写锁ReentrantReadWriteLock
读写所允许同一时刻被多个读线程访问,但是在写线程访问时,所有的读线程和其他的写线程都会被阻塞。
归纳总结:
- 公平性选择:支持非公平性(默认)和公平的锁获取方式,吞吐量还是非公平优于公平;
- 重入性:支持重入,同一线程读锁获取后能再次获取,同一线程写锁获取之后能够再次获取写锁,同时也能够获取读锁;
- 锁降级:遵循先获取写锁,然后获取读锁再释放写锁的次序,写锁能够降级成为读锁
要想能够彻底的理解读写锁必须能够理解这样几个问题:
- 读写锁是怎样实现分别记录读写状态的?
- 写锁是怎样获取和释放的?
- 读锁是怎样获取和释放的?
一、写锁:是独占式锁
1、写锁获取tryAcquire:在同一时刻写锁是不能被多个线程所获取,很显然写锁是独占式锁,而实现写锁的同步语义是通过重写AQS中的tryAcquire方法实现的。
tryAcquire其中,有个exclusiveCount(c)方法,其中有个int值EXCLUSIVE_MASK:同步状态的高16位用来表示读锁被获取的次数,同步状态的低16位用来表示写锁的获取次数;
tryAcquire主要逻辑为:当读锁已经被读线程获取或者写锁已经被其他写线程获取,则写锁获取失败;否则,获取成功并支持重入,增加写状态。
2、写锁释放tryRelease:写锁释放通过重写AQS的tryRelease方法
源码的实现逻辑与ReentrantLock基本一致,这里需要注意的是,减少写状态int nextc = getState() - releases;
只需要用当前同步状态直接减去写状态的原因正是我们刚才所说的写状态是由同步状态的低16位表示的。
二、读锁:不是独占式锁:是共享锁
读锁不是独占式锁,即同一时刻该锁可以被多个读线程获取也就是一种共享式锁。
实现共享式同步组件的同步语义需要通过重写AQS的tryAcquireShared方法和tryReleaseShared方法。
1、读锁的获取实现tryAcquireShared:
当写锁被其他线程获取后,读锁获取失败,否则获取成功利用CAS更新同步状态。
2、读锁释放的实现tryReleaseShared:
读锁释放将同步状态减去读状态即可;
三、锁降级:
读写锁支持锁降级:遵循按照先获取写锁,然后获取读锁再释放写锁的次序,写锁能够降级成为读锁,不支持锁升级(读锁不能升级成写锁);
四、总结:
非公平和公平的最大区别在于写锁的获取上:
- 在非公平策略中,写锁的获取永远不需要排队,这其实时性能优化的考虑,因为大多数情况写锁涉及的操作时间耗时要远大于读锁,频次远低于读锁,这样可以防止写线程一直处于饥饿状态。
- ReentrantReadWriteLock的特殊之处其实就是用一个int值表示两种不同的状态(低16位表示写锁的重入次数,高16位表示读锁的使用次数),并通过两个内部类同时实现了AQS的两套API,核心部分与共享/独占锁并无什么区别。