谈到并发,不得不谈ReentrantLock;而谈到ReentrantLock,不得不谈AbstractQueuedSynchronizer(AQS)!
/**
* Head of the wait queue, lazily initialized. Except for
* initialization, it is modified only via method setHead. Note:
* If head exists, its waitStatus is guaranteed not to be
* CANCELLED.
*/
private transient volatile Node head;
/**
* Tail of the wait queue, lazily initialized. Modified only via
* method enq to add new wait node.
*/
private transient volatile Node tail;
/**
* The synchronization state.
*/
private volatile int state;
AQS的成员变量不多
1. state(代表共享资源)
2.head, tail 来变量来维护一个FIFO线程等待队列 简称CLH(多线程争用资源被阻塞时会进入此队列)
CLH队列
static final class Node {
int waitStatus;
Node prev;
Node next;
Node nextWaiter;
Thread thread;
}
waitStatus:表示节点的状态,其中包含的状态有:
CANCELLED:值为1,表示当前节点被取消;
SIGNAL:值为-1,表示当前节点的的后继节点将要或者已经被阻塞,在当前节点释放的时候需要unpark后继节点;
CONDITION:值为-2,表示当前节点在等待condition,即在condition队列中;
PROPAGATE:值为-3,表示releaseShared需要被传播给后续节点(仅在共享模式下使用);
0:无状态,表示当前节点在队列中等待获取锁。
prev:前继节点;
next:后继节点;
nextWaiter:存储condition队列中的后继节点;
thread:当前线程。
AQS定义两种资源共享方式:。
1.当模式为Exclusive(独占,只有一个线程能执行,如ReentrantLock),程序会从CLH队列一个一个唤醒.
ReentrantLock.lock方法,调用AQS的acquire(1)
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
//尝试获取独占锁;
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
// 获取state
int c = getState();
// state=0表示当前队列中没有线程被加锁
if (c == 0) {
/*
* 首先判断是否有前继结点,如果没有则当前队列中还没有其他线程;
* 设置状态为acquires,即lock方法中写死的1(CAS来执行);
* 设置当前线程独占锁。
*/
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
/*
* 如果state不为0,表示已经有线程独占锁了,这时还需要判断独占锁的线程是否是当前的线程,原因是由于ReentrantLock为可重入锁;
* 如果独占锁的线程是当前线程,则将状态加1,并setState;
* 这里为什么不用compareAndSetState?因为独占锁的线程已经是当前线程,不需要通过CAS来设置。
*/
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
//该方法就是根据当前线程创建一个Node,然后添加到队列尾部
private Node addWaiter(Node mode) {
// 根据当前线程创建一个Node对象
Node node = new Node(Thread.currentThread(), mode);
Node pred = tail;
// 判断tail是否为空,如果为空表示队列是空的,直接enq
if (pred != null) {
node.prev = pred;
// 这里尝试CAS来设置队尾,如果成功则将当前节点设置为tail,否则enq
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
// 中断标志位
boolean interrupted = false;
for (;;) {
// 获取前继节点
final Node p = node.predecessor();
// 如果前继节点是head,则尝试获取
if (p == head && tryAcquire(arg)) {
// 设置head为当前节点(head中不包含thread)
setHead(node);
// 清除之前的head
p.next = null; // help GC
failed = false;
return interrupted;
}
// 如果p不是head或者获取锁失败,判断是否需要进行park
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
//如果在循环的过程中出现了异常,则执行cancelAcquire方法,用于将该节点标记为取消状态。
cancelAcquire(node);
}
}
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL) //只有在前继节点的状态是SIGNAL时,需要park
return true;
if (ws > 0) {
//如果ws > 0,CANCELLED表示已被取消,删除状态是已取消的节点;
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
//其他情况,设置前继节点的状态为SIGNAL
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);//阻塞,线程挂起
return Thread.interrupted();
}
该方法主要工作如下:
- 尝试获取独占锁;
- 获取成功则返回,否则执行步骤3;
- addWaiter方法将当前线程封装成Node对象,并添加到队列尾部;
- 自旋获取锁,并判断中断标志位。如果中断标志位为
true
,执行步骤5,否则返回; - 设置线程中断。
ReentrantLock.unlock方法,调用AQS的release(1)
public final boolean release(int arg) {
// 尝试释放锁
if (tryRelease(arg)) {
// 释放成功后unpark后继节点的线程
Node h = head;
if (h != null && h.waitStatus != 0)
//当前线程被释放之后,需要唤醒下一个节点的线程
unparkSuccessor(h);
return true;
}
return false;
}
protected final boolean tryRelease(int releases) {
// 这里是将锁的数量减1
int c = getState() - releases;
// 如果释放的线程和获取锁的线程不是同一个,抛出非法监视器状态异常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
// 由于重入的关系,不是每次释放锁c都等于0,
// 直到最后一次释放锁时,才会把当前线程释放
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
// 记录锁的数量
setState(c);
return free;
}
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
//从队列尾部向前遍历找到最前面的一个waitStatus小于0的节点
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);//唤醒
}
该方法主要工作如下:
- 尝试释放锁;
- 获取没完全释放(重入)则返回,否则执行步骤3;
- 释放成功后unpark后继节点的线程
2.当模式为Share(共享,多个线程可同时执行,如ReadWriteLock.readLock),程序会从CLH队列唤醒一个,给予资源,如果下一个也是共享型,则继续唤醒下一个,如果不是则等待资源。
ReadWriteLock的作用是,写的时候只能一个线程拥有锁,读的时候可以多个线程同时拥有。基中先看一下其state的设计
可以看到state的高16位代表读锁的个数;低16位代表写锁的状态。
//读锁获取共享锁直接用的是AQS中的
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
//读锁尝试获取共享锁
//exclusiveCount获取state低8位的数值,也就是写线程重入数。
//sharedCount获取state高8位数值,就是共享线程数
//用HoldCounter,其实是ThreadLocal保存共享线程的重入数,首个线程直接保存在firstReader ,firstReaderHoldCount。
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);
//如果读不应该阻塞并且读锁的个数小于最大值65535,并且可以成功更新状态值,成功
if (!readerShouldBlock() &&
r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) {
if (r == 0) {//第一个读线程就是当前线程
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {//重入线程
firstReaderHoldCount++;//重入数加1
} else {//当前读线程和第一个读线程不同,记录每一个线程读的次数
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
}
return 1;
}
//CAS循环尝试
return fullTryAcquireShared(current);
}
final int fullTryAcquireShared(Thread current) {
HoldCounter rh = null;
for (;;) {
int c = getState();
if (exclusiveCount(c) != 0) {//一旦有别的线程获得了写锁,返回-1,失败
if (getExclusiveOwnerThread() != current)
return -1;
} else if (readerShouldBlock()) {//如果读线程需要阻塞(如果是公平锁,则要排队)
if (firstReader == current) {//发现是自己占了锁,什么都不做
// assert firstReaderHoldCount > 0;
} else {//别的线程占用了,则返回-1表示获取锁失败。
if (rh == null) {
rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current)) {
rh = readHolds.get();
if (rh.count == 0)
readHolds.remove();
}
}
if (rh.count == 0)
return -1;
}
}
if (sharedCount(c) == MAX_COUNT)
throw new Error("Maximum lock count exceeded");
//如果成功更改状态,成功返回
if (compareAndSetState(c, c + SHARED_UNIT)) {
if (sharedCount(c) == 0) {
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {
firstReaderHoldCount++;
} else {
if (rh == null)
rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
cachedHoldCounter = rh; // cache for release
}
return 1;
}
}
}
//AQS中享锁的处理方式
private void doAcquireShared(int arg) {
//这是加入CLH的是Node.SHARED模式节点
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {//获取成功则更新CLH的头节点
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
//如果不是头节点,则去阻赛,等待唤醒
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
//独立模式和共享模式的主要区分点
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head;
setHead(node);
//如果获取成功,且下个节点也是共享模式,则继续唤醒下个结点
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
if (s == null || s.isShared())
doReleaseShared();
}
}
Condition的设计
Condition 是对java.lang.Object
上监视器方法,包括wait()
,wait(long timeout)
,notify()
,notifyAll()一种代替。可以更加易于理解。
public class ConditionObject implements Condition, java.io.Serializable {
private transient Node firstWaiter;
private transient Node lastWaiter;
private Node addConditionWaiter() {
Node t = lastWaiter;
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
Node node = new Node(Thread.currentThread(), Node.CONDITION);
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
}
//获取锁后的等待。。
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter();
....
}
//当条件发生后的唤醒
private void doSignal(Node first) {
do {
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
final boolean transferForSignal(Node node) {
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
//把节点加到CLH队列的后面
Node p = enq(node);
int ws = p.waitStatus;
//改变节点状态,试着唤醒一波
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
}
从实现类可以看出来了,唤醒过程如下
睡觉过程如下
总结:
AQS总的架构图
节点分为共享和独享2种模式,另外state的可以让继承类自由设计,Watiter队列和条件是一对一的。
AQS的成员变量不多
- state(代表共享资源)
- head, tail 来变量来维护一个FIFO线程等待队列 简称CLH(多线程争用资源被阻塞时会进入此队列)
- hread(当前执行的线程)
独占模式Exclusive,以ReentrantLock为例
- state 为 0 ,thread = null
- 线程A请求资源,state以cas方式设置为1,thread设置为线程A
- 如果线程A再次请求资源(重入),state + 1
- 线程B请求资源,加入到CLH队列中阻塞,等待唤醒
- 线程A释放资源state=0,唤醒CLH队列中head结点,去用compareAndSwap去进行锁的占用
- 此时线程C请求资源,资源state=0,如果是NonfairSync非公平锁状态,不判断是否有等待队列,直接使用compareAndSwap去进行锁的占用
- 此时线程C请求资源,资源state=0,如果是fairSync公平锁状态,判断是否有等待队列,如果有则将自己加到等待队列尾
共享模式Share,以ReadWriteLock为例
- state的高16位代表读锁的个数;低16位代表写锁的状态
- 线程A请求写资源,低16位state = 1
- 线程B请求读资源,加入到CLH队列中阻塞标记为Share
- 线程C请求读资源,加入到CLH队列中阻塞标记为Share
- 线程D请求写资源,加入到CLH队列中阻塞标记为Exclusive
- 线程A释放写资源,低16位state = 0,唤醒CLH队列中head(B)结点,发现head结点为Share,继续唤醒head(C),最终停于head(D).
- 线程B,C获取读资源,高16位state=2
Condition的设计
Condition 是对java.lang.Object上监视器方法,包括wait(),wait(long timeout),notify(),notifyAll()一种代替。
- 每以锁创建一个Condition,则会自动生成一个waiter队列
- 当以Condition的wait时候,生成节点加入waiter队列
- 当以Condition的notify时候,waiter队列关结点,加入到CLH队列中