为什么要单独将这部分抽出来总结?
纵观JUC下基于AQS实现的工具类,其实现思想都很统一:
- state代表状态值
- 通过try*()方法尝试单次更改state,根据state当前情况决定是否进入do*()中
- 一旦进入了do*()中,就代表了可能加入同步/等待队列中进行阻塞等待
- 而当线程从阻塞苏醒后,又会通过try*()方法尝试单次更改state,并根据state当前情况决定是否设置自身为新的头节点或阻塞与否
因此锁的获取能否,关键还是看子类复写的try*()方法,AQS自身提供的其他的方法都是对获取成功或失败的辅助工作
本文单独将try*()提出来,下文将do*()提出来,从而更好的理解JUC的设计思想
暴露方法
// 尝试获取 独占锁
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
// 尝试释放 独占锁
protected boolean tryRelease(int arg) {
throw new UnsupportedOperationException();
}
//尝试获取 共享锁
protected int tryAcquireShared(int arg) {
throw new UnsupportedOperationException();
}
//尝试释放 共享锁
protected boolean tryReleaseShared(int arg) {
throw new UnsupportedOperationException();
}
//判断是否时当前线程在持有锁
protected boolean isHeldExclusively() {
throw new UnsupportedOperationException();
}
以上五个方法时AQS向外暴露的方法,子类只需要覆盖这几个方法即可注意着五个方法并不是抽象方法,因此子类并不是必须全部覆盖这些方法。JUC中实现这些方法的子类有
独占锁是仅能有一个线程持有锁,而共享锁是可以同时多个线程持有锁的,因此独占锁和共享锁的实现方式分开实现
除了以上的四种try*()
方法外,AQS自身实现了诸多的如acquire
和doAcquire()
等方法,他们之间的区别在于try*()
方法代表一次尝试性的获取锁操作,如果获取到了就拿到了锁,否则直接返回。而AQS自身实现的acquire
和doAcquire()
等方法如果获取不到锁会能够进入同步/等待队列中阻塞等待进行锁的争夺,直到拿到了锁返回。对于共享锁,try*()
也会进行自旋获取,因为共享锁可以被多个线程持有
AQS通过一个private volatile int state;
来表示锁的状态,当state=0时代表无锁,>0代表有锁,>1代表重入次数
AQS从AOS继承了private transient Thread exclusiveOwnerThread;
用来记录当前持有独占锁的线程
下面介绍不同子类的try*()
方法实现
不同的子类实现
ReentrantLock
ReentrantLock
实现了独占锁的尝试获取和尝试释放方法,因此我们从这里可以看出ReentrantLock
是一种独占锁
- 非公平锁:线程获取锁不会排队,谁拿到算谁的
protected final boolean tryAcquire(int acquires){
final Thread current = Thread.currentThread();
int c = getState();
// state==0代表当前没有锁,可以进行获取
if (c == 0) {
//CAS设置state为acquires,成功后标记exclusiveOwnerThread为当前线程
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//代表重入,满足条件直接进行state的增加,因为持有锁,所以不需要CAS
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
// 调用该方法表明该线程是持有锁的,因此不需要CAS操作
protected final boolean tryRelease(int releases){
int c = getState() - releases;
// 如果当前线程不是持有锁的线程,抛出异常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
//锁释放完了
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
//free默认是false,只有c==0时才被置为true,否则都是false代表没有获取到锁
return free;
}
- 公平锁:线程获取锁会排队,按照顺序的获取
// 获取state,如果为0,则若同步队列中没有前驱节点了(与非公平的主要区别),则CAS设置state为acquires,成功后标记exclusiveOwnerThread为当前线程
protected final boolean tryAcquire(int acquires){
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 这里是唯一不同的地方,这里会判断是否还有前驱节点,直接自己为头节点了或者同步队列空了才会继续后面的锁的获取操作
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
// 与非公平锁相同
protected final boolean tryRelease(int releases){}
ReentrantReadWriteLock
该类的内部抽象类Sync
实现了上述的四种方法,因此可以得出结论ReentrantReadWriteLock
提供独占锁(Write)和共享锁(Read)
state的高16位代表读锁的个数,低16位代表写锁的状态;因此在写锁的state更新时直接加减数量即可,而进行读锁的加减时,是根据1<<16==65535
进行的
protected final boolean tryAcquire(int acquires) {
/*
* Walkthrough:
* 1. If read count nonzero or write count nonzero
* and owner is a different thread, fail.
*
* 如果读锁数量不为0 || (写锁数量不为零且当前线程不是持有锁的线程,即不是重入),失败
*
* 2. If count would saturate, fail. (This can only
* happen if count is already nonzero.)
*
* 如果锁数量益处,失败
*
* 3. Otherwise, this thread is eligible for lock if
* it is either a reentrant acquire or
* queue policy allows it. If so, update state
* and set owner.
*
* 否则如果线程是一个重入锁,允许
* 或者是根据公平/非公平的队列策略来决定是否允许
*/
Thread current = Thread.currentThread();
int c = getState();
//从state中提取写锁的数量
int w = exclusiveCount(c);
if (c != 0) {
// 如果 c != 0 and w == 0 则读锁数量为0,若不是重入获取,则拒绝
if (w == 0 || current != getExclusiveOwnerThread())
return false;
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// 重入锁,允许
setState(c + acquires);
return true;
}
//在公平锁中,有前驱节点时返回true(会直接返回false),否则返回false
//在非公平锁中,总是返回false
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
setExclusiveOwnerThread(current);
return true;
}
protected final boolean tryRelease(int releases) {
//如果不是当前线程在持有锁,错误
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
int nextc = getState() - releases;
// 注意这里是独占锁的释放,因此要判断独占锁数量是否已经减为0才能作为free的依据
boolean free = exclusiveCount(nextc) == 0;
if (free)
setExclusiveOwnerThread(null);
setState(nextc);
return free;
}
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,否则false
//非公平下,同步队列中的第一个排队线程处于独占模式,返回true,否则false
//只有readerShouldBlock()返回false,即公平下不存在前驱或非公平下第一个排队的不是独占模式,才能进行后面的读锁获取操作
if (!readerShouldBlock() &&
r < MAX_COUNT &&
//SHARED_UNIT=1<<16 因为state的高16位代表读锁数量
compareAndSetState(c, c + SHARED_UNIT)) {
//如果之前的读锁为0,说明该线程是第一个获取读锁的
if (r == 0) {
//引入这俩的意义是如果只是一个线程在获取读锁,就无需再从HoldCounter中获取了
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {
//读锁的重入
firstReaderHoldCount++;
} else {
//在本次获得读锁时发现已经有其他线程持有着读锁了
/**
static final class HoldCounter {
int count = 0;
// Use id, not reference, to avoid garbage retention
final long tid = getThreadId(Thread.currentThread());
}
static final class ThreadLocalHoldCounter
extends ThreadLocal<HoldCounter> {
public HoldCounter initialValue() {
return new HoldCounter();
}
}
*/
//HoldCounter 保存了线程id及线程持有的读锁数量(使用id不用引用是防止GC不回收造成内存泄露)
//readHolds 是一个ThreadLocalHoldCounter,它是一个ThreadLocal
//cachedHoldCounter是上一个线程的HoldCounter,记录着最后一个线程的读锁数量
//因为读锁是很多线程可以获取的,因此对于某个线程而言,它应该记住自己获取的读锁数量,不应该操作其他线程的读锁
//这里通过HoldCounter进行记录并通过ThreadLocal进行线程绑定
//cachedHoldCounter是记录了上个线程的标识,这里可以根据这个来直接判断上个操作线程是不是自己,如果是自己就不需要再从ThreadLocal中获取了
HoldCounter rh = cachedHoldCounter;
// 如果cachedHoldCounter为空则将其设置为当前线程的HoldCounter
//如果不为空则判断是否是自己线程的,如果不是,则替换成自己的
if (rh == null || rh.tid != getThreadId(current))
cachedHoldCounter = rh = readHolds.get();
// 这里rh已经是自己线程的 HoldCounter 了,
else if (rh.count == 0)
readHolds.set(rh);
//自己线程的读锁数量+1,自己的HoldCounter 里放的是rh对象,这里的自增对其也是可见的
rh.count++;
}
//成功获取读锁,返回1
return 1;
}
//这个方法会去自旋获取,为什么这里不是一次性的而要自旋呢?因为读锁可以被多个线程获取,此次CAS失败只是有线程竞争,另外一个线程拿到读锁后当前线程便可以拿到锁而无需等释放。因此去自旋获取
return fullTryAcquireShared(current);
}
protected final boolean tryReleaseShared(int unused) {
Thread current = Thread.currentThread();
//第一个拿到读锁的线程直接在这里释放,无需读取HoldCounter
if (firstReader == current) {
// assert firstReaderHoldCount > 0;
if (firstReaderHoldCount == 1)
firstReader = null;
else
firstReaderHoldCount--;
} else {
//如果上个线程是自己,直接用cachedHoldCounter而不需要再去ThreadLocal中取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 (;;) {
//将读锁释放更新到state上
int c = getState();
int nextc = c - SHARED_UNIT;
if (compareAndSetState(c, nextc))
// Releasing the read lock has no effect on readers,
// but it may allow waiting writers to proceed if
// both read and write locks are now free.
return nextc == 0;
}
}
可以看到一系列优化手段
firstReader
等是为了如果平时没有锁竞争时,直接使用该缓存
cachedHoldCounter
则是当上个线程是当前线程时无需再去ThreadLocal
中取`HoldCounter
Semaphore
//非公平锁的实现,很简单粗暴,自旋获取
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
//公平锁的实现,与非公平相比,就是多了个判断前驱节点的条件,如果存在前驱就代表无法获取锁
protected int tryAcquireShared(int acquires) {
for (;;) {
if (hasQueuedPredecessors())
return -1;
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
CountDownLatch
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
与其他的不同,CountDownLatch
其实是初始化时持有了多把共享锁,每次CountDown
就调用tryReleaseShared
释放共享锁。而await
的线程会去调用tryAcquireShared
去尝试获取锁,但这里定制化只有state==0
才会返回1代表能够获取锁,因此如果条件不满足会失败,从而await
的线程会进入后面的阻塞流程
ThreadPoolExecotor
private final class Worker extends AbstractQueuedSynchronizer implements Runnable
protected boolean tryAcquire(int unused) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
protected boolean tryRelease(int unused) {
setExclusiveOwnerThread(null);
setState(0);
return true;
}
线程池的work线程就是最简单的独占锁的获取和释放,不再多说了