1.基本概念
读写锁是基于AQS构建,它包含有读锁和写锁,含有公平和非公平机制。Sync继承AbstractQueuedSynchronizer,它是构建锁的核心。
业务场景就是对一个共享资源的读频率大于写。
public class ReentrantReadWriteLock
implements ReadWriteLock, java.io.Serializable {
private static final long serialVersionUID = -6992448646407690164L;
/** 读锁 */
private final ReentrantReadWriteLock.ReadLock readerLock;
/** 写锁 */
private final ReentrantReadWriteLock.WriteLock writerLock;
/** 基于AQS的同步机制 */
final Sync sync;
ReentrantReadWriteLock具有以下特性:
- 非公平模式(默认方式)和公平模式,公平模式就是线程按照队列的顺序获取锁,非公平模式具有更高的吞吐量。
- 可重入性,一个锁可以重复获取。
- 锁降级,可以从写锁降为读锁,方法是获取写锁,然后是读锁,然后释放写锁。 但是,无法从读锁升级到写锁。
- 锁获取中断,在获取读锁和写锁时支持中断。
2. 源码解析
2.1 构造一个ReentrantReadWriteLock。
/**
* 默认是非公平模式
*/
public ReentrantReadWriteLock() {
this(fair);
}
/**
* fair决定是公平模式还是非公平模式
*/
public ReentrantReadWriteLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
readerLock = new ReadLock(this);
writerLock = new WriteLock(this);
}
NonfairSync和FairSync都继承Sync(Sync继承AQS)。都有writerShouldBlock和readerShouldBlock,判断当前线程是否应该阻塞,在非公平和公平模式下得判断集中不同,源码如下:
/**
* Nonfair version of Sync
*/
static final class NonfairSync extends Sync {
private static final long serialVersionUID = -8159625535654395037L;
final boolean writerShouldBlock() {
return false; // 非公平模式下写锁不会阻塞,即无视队列,不需排队
}
final boolean readerShouldBlock() {
/* As a heuristic to avoid indefinite writer starvation,
* block if the thread that momentarily appears to be head
* of queue, if one exists, is a waiting writer. This is
* only a probabilistic effect since a new reader will not
* block if there is a waiting writer behind other enabled
* readers that have not yet drained from the queue.
*/
return apparentlyFirstQueuedIsExclusive();
}
}
/**
* Fair version of Sync
*/
static final class FairSync extends Sync {
private static final long serialVersionUID = -2274990926593161451L;
final boolean writerShouldBlock() {
return hasQueuedPredecessors();
}
final boolean readerShouldBlock() {
return hasQueuedPredecessors();
}
}
2.2 读锁源码解析,基于AQS共享模式acquireShared,多线程通过tryAcquireShared方法获取共享锁,返回小于0获取失败,反之获取成功。
/**
*如果写锁没有被另一个线程持有,则获取读锁并立即返回。
*如果写锁被另一个线程持有,那么当前线程将被禁用以用于线程调度目的 并处于休眠状态,直到获得读锁为止。
*/
public void lock() {
sync.acquireShared(1);
}
public final void acquireShared(int arg) {
/**
*尝试获取锁。
*/
if (tryAcquireShared(arg) < 0)
/**
*尝试锁失败时,放入队列。
*/
doAcquireShared(arg);
}
protected final int tryAcquireShared(int unused) {
/*
* Walkthrough:
*/
Thread current = Thread.currentThread();
int c = getState();
/*
* 如果写锁重入次数不等于0,则表示写锁被某个线程占有
* 并且获取独占锁的线程不是当前线程,则返回-1
*/
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
/*
* 获取读锁计数
*/
int r = sharedCount(c);
/*
* 获取写锁持有线程数
* readerShouldBlock调用apparentlyFirstQueuedIsExclusive:如果请求读锁的当前线程发现同步队列的 head 节点的下一个节点为排他式节点,那么就说明有一个线程在等待获取写锁(争抢写锁失败,被放入到同步队列中),那么请求读锁的线程就要阻塞
*/
if (!readerShouldBlock() &&
r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) {
if (r == 0) {
/*
* 第一个获取读锁,记录为firstReader
*/
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {
firstReaderHoldCount++;
} else {
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
//计数器加1
rh.count++;
}
return 1;
}
/*
* 获取失败自旋重试
*/
return fullTryAcquireShared(current);
}
2.3 通过releaseShared释放锁。
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
/**
* 尝试释放读锁
*/
protected final boolean tryReleaseShared(int unused) {
Thread current = Thread.currentThread();
//当前线程为第一个读线程
if (firstReader == current) {
//第一个读线程锁计数1,则直接赋值为null
if (firstReaderHoldCount == 1)
firstReader = null;
else
//第一个读线程锁计数减1
firstReaderHoldCount--;
} else {
//从缓存计数器获取
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
rh = readHolds.get();
int count = rh.count;
if (count <= 1) {
readHolds.remove();
if (count <= 0)
throw unmatchedUnlockException();
}
//计数器减1
--rh.count;
}
// 自旋 CAS比较设置
for (;;) {
int c = getState();
int nextc = c - SHARED_UNIT;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}