这是我参与11月更文挑战的第25天,活动详情查看:2021最后一次更文挑战
ReentrantWriteLock概述
在实际开发中,往往会有写少读多的场景,显然ReentrantLock满足不了这个需求,所以ReentrantWriteLock应运而生。其采用读写分离的策略,允许多个线程可以同时获取到锁。
读写锁内部维护了一个ReadLLock和一个WriteLock,它们依赖Sync实现具体功能。而Sync继承自AQS,并且也提供了公平和非公平的实现。我们下面介绍一下非公平锁的读写锁实现。AQS中维护了一个state状态,而读写锁需要维护读写两个状态,于是可以利用state的高16表示读状态,低16位表示写状态。
static final int SHARED_SHIFT = 16;
//共享锁状态单位值
static final int SHARED_UNIT = (1 << SHARED_SHIFT);
/共享锁线程最大个数65536
static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1;
//排它锁(写锁)掩码,二进制15个1
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
//返回读锁线程数
static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
//返回写锁线程数
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
读写锁中的firstReader用来记录第一个获取到读锁的线程,firstReaderHoldCount则记录第一个获取到读锁的线程获取的可重入的次数。cachedHoldCounter用来记录最后一个获取读锁的线程获取读锁的可重入次数。
static final class HoldCounter {
int count = 0;
// Use id, not reference, to avoid garbage retention
final long tid = getThreadId(Thread.currentThread());
}
readHolder是ThreadLocal变量,用来存放除去第一个获取读锁线程外,其他线程获取读锁的可重入次数。ThreadLocalHoldCounter对象继承了ThreadLocal,因而initialValue方法返回一个HoldCounter对象。
static final class ThreadLocalHoldCounter
extends ThreadLocal<HoldCounter> {
public HoldCounter initialValue() {
return new HoldCounter();
}
}
ReentrantReadWriteLock支持以下功能:
-
支持公平和非公平的获取锁的方式;
-
支持可重入。读线程在获取了读锁后还可以获取读锁;写线程在获取了写锁之后既可以再次获取写锁又可以获取读锁;
-
还允许从写入锁降级为读取锁,其实现方式是:先获取写入锁,然后获取读取锁,最后释放写入锁。但是,从读取锁升级到写入锁是不允许的;
-
读取锁和写入锁都支持锁获取期间的中断;
-
Condition支持。仅写入锁提供了一个 Conditon 实现;读取锁不支持 Conditon ,readLock().newCondition() 会抛出 UnsupportedOperationException。
ReentrantWriteLock写锁的获取与释放(WriteLock)
- void lock()
写锁是个独占锁,只有一个线程可以同时持有该锁。如果当前线程没有线程获取到读锁和写锁,则当前线程可以获取到写锁然后返回。如果当前已经有线程获取到读锁和写锁,则请求写锁的线程会被阻塞挂起。如下代码所示,lock()内部调用了AQS的acquire方法,其中tryAcquire()方法是内部的sync类重写的。
public void lock() {
sync.acquire(1);
}
public final void acquire(int arg) {
//sync重写的tryAcquire方法
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
如下代码,如果当前AQS状态值不为0(c!=0)则说明当前已经有线程获取到了读锁或者写锁。接下来继续判断,如果w==0说明state的低16位为0,则表示已经有线程获取到了读锁,直接返回false。如果w!=0,且当前线程不是写锁拥有者,返回false。如果前面的情况都不是,则说明当前线程获取了写锁,修改写锁次数,返回true。
protected final boolean tryAcquire(int acquires) {
Thread current = Thread.currentThread();
int c = getState();
int w = exclusiveCount(c);
if (c != 0) {
// (Note: if c != 0 and w == 0 then shared count != 0)
if (w == 0 || current != getExclusiveOwnerThread())
return false;
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// Reentrant acquire
setState(c + acquires);
return true;
}
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
setExclusiveOwnerThread(current);
return true;
}
如果当前AQS状态值为0(c=0),表示当前没有线程获取到读锁和写锁。 其中writerShouldBlock()分为公平和非公平实现,非公平直接返回false,然后执行CAS抢占获取锁。公平锁是采用hasQueuedPredecessors来判断当前节点是否有前驱节点,如果有则当前线程则放弃获取写锁的权限,返回false。
//公平
final boolean writerShouldBlock() {
return hasQueuedPredecessors();
}
//非公平
final boolean writerShouldBlock() {
return false();
}
- boolean tryLock()
尝试获取写锁,如果当前没有其他线程持有任何锁,则当前线程获取写锁会成功,然后返回true。如果当前已经有其他线程持有写锁或者读锁则该方法直接返回false,不会阻塞。如果当前线程已经持有了该写锁则简单增加AQS的state值后返回true。
public boolean tryLock( ) {
return sync.tryWriteLock();
}
final boolean tryWriteLock() {
Thread current = Thread.currentThread();
int c = getState();
if (c != 0) {
int w = exclusiveCount(c);
if (w == 0 || current != getExclusiveOwnerThread())
return false;
if (w == MAX_COUNT)
throw new Error("Maximum lock count exceeded");
}
if (!compareAndSetState(c, c + 1))
return false;
setExclusiveOwnerThread(current);
return true;
}
- void unlock()
尝试释放锁,如果当前线程持有该锁,调用该方法会让该线程对该线程持有的AQS状态值减1,如果减1后状态值为0则当前线程会释放掉该锁。
public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
if (tryRelease(arg)) {
//激活阻塞队列中的一个线程
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
protected final boolean tryRelease(int releases) {
//判断是否是写锁拥有者调用的unlock
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
//获取可state值
int nextc = getState() - releases;
boolean free = exclusiveCount(nextc) == 0;
//如果当前state=0则释放锁
if (free)
setExclusiveOwnerThread(null);
setState(nextc);
return free;
}
ReentrantWriteLock读锁的获取与释放(ReadLock)
- void lock()
获取读锁,如果当前没有其他线程持有写锁,则当前线程可以获取读锁,AQS的状态值state的高16位的值会增加1,然后返回。否则其他的线程持有写锁,则当前线程会被阻塞。
public void lock() {
sync.acquireShared(1);
}
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
在上面代码中,读锁的lock方法调用了AQS的acquireShared方法,在其内部调用了ReentrantReadWriteLock中的sync重写的tryAcquire
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);
//尝试获取锁,多个线程抢只有一个会成功,不成功会执行fullTryAcquireShared(自旋)
// 读线程是否应该被阻塞、并且小于最大值、并且CAS设置成功
//如果全都满足,则开始抢锁
if (!readerShouldBlock() &&
r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) {
//如果是该线程第一个线程获取到读锁
if (r == 0) {
//设置第一个读线程,和占用次数
firstReader = current;
firstReaderHoldCount = 1;
//如果和第一个获取到读锁的线程相同
} else if (firstReader == current) {
//第一个线程占用次数+1
firstReaderHoldCount++;
} else {//读锁数量不为0,并且不为当前线程
//获取计数器
//cachedHoldCounter记录最后一个获取读锁的线程
HoldCounter rh = cachedHoldCounter;
//计数器为空或者计数器的tid不为当前正在运行的线程的tid
//则设置最后一个读线程为当前线程
//readHolds是ThreadLocal变量,存放当前线程的读锁次数
if (rh == null || rh.tid != getThreadId(current))
cachedHoldCounter = rh = readHolds.get();
//说明当前线程就是最后一个读线程
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
}
return 1;
}
//自旋获取
return fullTryAcquireShared(current);
}
上面的代码中,首先获取AQS的状态值,然后查看是否有其他线程获取了写锁,如果是则返回-1,调用AQS的doAcquireShared方法把当前线程放入AQS阻塞队列。如果当前线程持有了写锁也可以持有读锁(释放锁时,读写锁都需要释放)。
- void unlock()方法
public void unlock() {
sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
我们主要介绍一下tryReleaseShared方法。
protected final boolean tryReleaseShared(int unused) {
//获取当前线程,如果当前线程为第一个读线程,
//则如果读线程占用的资源数为,firstReader置为空
Thread current = Thread.currentThread();
if (firstReader == current) {
// assert firstReaderHoldCount > 0;
if (firstReaderHoldCount == 1)
firstReader = null;
else
firstReaderHoldCount--;
} else {//如果不是当前线程,则通过HoldCounter获取本线程的读锁次数(本地变量)
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();
}
--rh.count;
}
for (;;) {
int c = getState();
int nextc = c - SHARED_UNIT;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
最后我们用一个图来总结读写锁: