针对读多写少的场景,Java提供了另外一个实现Lock接口的读写锁ReentrantReadWriteLock(RRW),之前分析过ReentrantLock是一个独占锁,同一时间只允许一个线程访问。
而 RRW 允许多个读线程同时访问,但不允许写线程和读线程、写线程和写线程同时访问。
读写锁内部维护了两个锁,一个是用于读操作的ReadLock,一个是用于写操作的 WriteLock。
读写锁遵守以下三条基本原则
- 允许多个线程同时读共享变量;
- 只允许一个线程写共享变量;
- 如果一个写线程正在执行写操作,此时禁止读线程读共享变量。
读写锁如何实现
RRW也是基于AQS实现的,它的自定义同步器(继承自AQS)需要在同步状态state上维护多个读线程和一个写线程的状态。RRW的做法是使用高低位来实现一个整形控制两种状态,一个int占4个字节,一个字节8位。所以高16位表示读,低16位表示写。
abstract static class Sync extends AbstractQueuedSynchronizer {
static final int SHARED_SHIFT = 16;
// 10000000000000000(65536)
static final int SHARED_UNIT = (1 << SHARED_SHIFT);
// 65535
static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1;
//1111111111111111
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
// 读锁(共享锁)的数量,只计算高16位的值
static int sharedCount(int c) {
return c >>> SHARED_SHIFT; }
// 写锁(独占锁)的数量
static int exclusiveCount(int c) {
return c & EXCLUSIVE_MASK; }
}
获取读锁
当线程获取读锁时,首先判断同步状态低16位,如果存在写锁,则获取锁失败,进入CLH队列阻塞,反之,判断当前线程是否应该被阻塞,如果不应该阻塞则尝试 CAS 同步状态,获取成功更新同步锁为读状态。
protected final int tryAcquireShared(int unused) {
Thread current = Thread.currentThread();
int c = getState();
// 如果当前已经有写锁了,则获取失败
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
// 获取读锁数量
int r = sharedCount(c);
// 非公平锁实现中readerShouldBlock()返回true表示CLH队列中有正在排队的写锁
// CAS设置读锁的状态值
if (!readerShouldBlock() &&
r < MAX_COUNT &&
compareAndS