一、作用
简单说:AQS就是在多线程抢夺共享资源的时候,实现了统一规划资源,确保只有一个线程抢夺成功,其他的都排队的框架
AQS核心思想:如果被请求的共享资源空闲,那么当前线程设置为独占线程,将当前资源设定为锁定状态,如果有其他线程也要访问这个被锁定的共享资源,那么就需要一套线程阻塞及被唤醒的分配机制,aqs是通过CLH队列锁实现的,将获取不到锁的线程加到队列中。
CLH是啥:FIFO双向队列, 如下图(图片来自网络):
AQS的CLH实现:
static final class Node {
/** 标识节点在共享模式下等待*/
static final Node SHARED = new Node();
/** 标识节点在独占模式下等待*/
static final Node EXCLUSIVE = null;
/** waitStatus 当前线程被取消 */
static final int CANCELLED = 1;
/** waitStatus 当前节点的后继节点包含的线程需要运行,也就是unpark */
static final int SIGNAL = -1;
/** waitStatus 表示当前节点在等待condition,也就是在condition队列中 */
static final int CONDITION = -2;
/**
* waitStatus 表示当前场景下后续的acquireShared能够得以执行
*/
static final int PROPAGATE = -3;
volatile int waitStatus;
/**
* 节点的前驱节点
*/
volatile Node prev;
/**
* 节点的后继节点
*/
volatile Node next;
/**
* 当前的线程
*/
volatile Thread thread;
/**
* 队列中下一个等待的线程
*/
Node nextWaiter;
/**
* 如果节点在共享模式下等待,则返回true。
*/
final boolean isShared() {
return nextWaiter == SHARED;
}
/**
* 返回当前节点的前驱节点
*/
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
Node() {
}
Node(Thread thread, Node mode) { // Used by addWaiter
this.nextWaiter = mode;
this.thread = thread;
}
Node(Thread thread, int waitStatus) { // Used by Condition
this.waitStatus = waitStatus;
this.thread = thread;
}
}
二、waitStatus 结点状态
- CANCELLED,值为1,表示当前的线程被取消
- SIGNAL,值为-1,表示当前节点的后继节点包含的线程需要运行,也就是unpark
- CONDITION,值为-2,表示当前节点在等待condition,也就是在condition队列中
- PROPAGATE,值为-3,表示当前场景下后续的acquireShared能够得以执行
- 值为0,表示当前节点在sync队列中,等待着获取锁
三、核心acquire 说明(此部分相当于ReentrantLock.lock()的过程)
从ReentrantLock的lock开始看吧
new ReentrantLock()默认构造是非公平锁,是否公平主要取决于是否按顺序去争抢共享资源。
public void lock() {
sync.lock(); //加锁开始
}
有两种是实现一种是公平一种是非公平,这里是非公平
非公平锁
/**
* Sync object for non-fair locks
*/
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final void lock() {
//直接cas方式去抢锁如果抢成功则设置当前新线程为独占线程。(1)
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
公平锁,区别就在于尝试加锁的时候,如上(1)非公平锁在当前资源没有被任何线程抢占的时候能直接抢到资源,公平锁则会进行hasQueuedPredecessors判断,也就是严格按照加入队列的顺序,FIFO。
非公平锁的性能高于公平锁的原因:在恢复一个被挂起的线程与该线程真正运行之间存在着严重的延迟。
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
/**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*/
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;
}
}
//判断head和tail不相等(说明有等待线程)并且(head.next为null=>说明有线程正在入队列的中间状态,肯定不是当前线程, 因为一个线程一个时间只能做一件事 或者 head.next.thread不是当前线程),主要是防止中间状态的时候导致不公平的因素出现,那么我理解非公平的吞吐量肯定更好好于公平的。
public final boolean hasQueuedPredecessors() {
// The correctness of this depends on head being initialized
// before tail and on head.next being accurate if the current
// thread is first in queue.
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
第一次CAS失败进入这里
public final void acquire(int arg) {
if (!tryAcquire(arg) && //如果tryAcquire失败了,那就该进入CLH队列中排队了。
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState(); //获取当前状态
if (c == 0) {//代表未被占用
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);//设置为独占
return true;
}
}
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;
}
tryAcquire 执行失败,则进入排队
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode); //构造一个新的节点
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
node.prev = pred;//当前队列尾节点作为当前节点的头结点
if (compareAndSetTail(pred, node)) {//cas 方式设置当前节点作为队列尾节点
pred.next = node; //设置之前的尾节点的后继节点为当前节点。
return node;
}
}
enq(node);//当前队列不存在,需要初始化。
return node;
}
队列初始化过程
private Node enq(final Node node) {
for (;;) { //这里多说一句 for这种用法和while(true)一致
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))// 各种cas操作
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {//如果有一个节点,直接把当前节点作为尾节点
t.next = node;
return t;
}
}
}
}
tryAcquire() 和 addWaiter()获取资源失败,放入队列尾部,则进行下一步:
自旋一直try
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true; //标识位 默认是true是代表拿到了资源
try {
boolean interrupted = false; //代表是否被中断过,这个在parkAndCheckInterrupt里面说比较合适
for (;;) {//又自旋
final Node p = node.predecessor();//前驱节点
//只有前驱是头结点了,他才能参与抢资源,不是老二就都没有资格而且只有当前头结点释放资源了或者被中断了他才能抢到。。
if (p == head && tryAcquire(arg)) {//自旋一直去尝试获取资源
setHead(node);//获取到了则将
p.next = null; // help GC 方便GC这个不用说了
failed = false;
return interrupted;
}
//进入waiting状态,直到被中断或者被调用unpark,详情看下面的分析
if (shouldParkAfterFailedAcquire(p, node) && //
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
Node.SIGNAL:waitStatus值,指示后续线程需要取消waiting(也就是unpark或者中断)
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus; //前驱节点的状态
if (ws == Node.SIGNAL) //是前驱节点就返回true
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
if (ws > 0) {//说明前驱节点已经被取消了,
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do {
node.prev = pred = pred.prev; //然后就跳过当前无效节点去找有效的。
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
* waitStatus must be 0 or PROPAGATE. Indicate that we
* need a signal, but don't park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);//如果有效设置前驱节点的状态为SIGNAL
}
return false;
}
设置当前线程为等待状态且看线程是否被中断过,并清除中断标识。
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
总结:
- 调用tryAcquire() 尝试直接去获取资源,如果成功则直接返回;
- 没成功,则addWaiter() 将该线程加入等待队列的尾部,并标记为独占模式;
- acquireQueued()让线程在等待队列中处于waiting状态,当轮到当前线程,也就是当前成为头结点的后继节点,则会被unpark())会去尝试获取资源。获取到资源后才返回。如果在整个等待过程中被中断过,则返回true,否则返回false。
- 如果线程在等待过程中被中断过,哪怕一次,也是不会干活的,会在抢到资源后进行selfInterrupt()自己中断自己,有点后置。
四、核心release说明(此部分相当于ReentrantLock.unlock()的过程)
release相当于acquire的相反操作,也就是释放资源,释放指定量的资源。
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;
}
tryRelease是ReentrantLock中的实现
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);
return free;
}
唤醒等待队列中下一个线程
private void unparkSuccessor(Node node) {
/*
* If status is negative (i.e., possibly needing signal) try
* to clear in anticipation of signalling. It is OK if this
* fails or if status is changed by waiting thread.
*/
int ws = node.waitStatus;
if (ws < 0)//如果状态为负(即可能需要信号),则尝试在预期信号时清除。如果失败或者状态被等待线程更改,这是正常的。
compareAndSetWaitStatus(node, ws, 0);//head节点状态设置成0
/*
* Thread to unpark is held in successor, which is normally
* just the next node. But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
*/
//unpark的线程保存在后续节点中,后者通常只是下一个节点。但如果被取消或明显为空,则从尾部向后移动以找到实际的未取消的继承者。
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);
}
总结:
只有(state=0)才会将资源彻底释放。
参考资料
线程状态说明 1
线程状态说明 2
从ReentrantLock的lock开始看吧
new ReentrantLock()默认构造是非公平锁,是否公平主要取决于是否按顺序去争抢共享资源。
public void lock() {
sync.lock(); //加锁开始
}
有两种是实现一种是公平一种是非公平,这里是非公平
非公平锁
/**
* Sync object for non-fair locks
*/
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final void lock() {
//直接cas方式去抢锁如果抢成功则设置当前新线程为独占线程。(1)
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
公平锁,区别就在于尝试加锁的时候,如上(1)非公平锁在当前资源没有被任何线程抢占的时候能直接抢到资源,公平锁则会进行hasQueuedPredecessors判断,也就是严格按照加入队列的顺序,FIFO。
非公平锁的性能高于公平锁的原因:在恢复一个被挂起的线程与该线程真正运行之间存在着严重的延迟。
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
/**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*/
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;
}
}
//判断head和tail不相等(说明有等待线程)并且(head.next为null=>说明有线程正在入队列的中间状态,肯定不是当前线程, 因为一个线程一个时间只能做一件事 或者 head.next.thread不是当前线程),主要是防止中间状态的时候导致不公平的因素出现,那么我理解非公平的吞吐量肯定更好好于公平的。
public final boolean hasQueuedPredecessors() {
// The correctness of this depends on head being initialized
// before tail and on head.next being accurate if the current
// thread is first in queue.
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
第一次CAS失败进入这里
public final void acquire(int arg) {
if (!tryAcquire(arg) && //如果tryAcquire失败了,那就该进入CLH队列中排队了。
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState(); //获取当前状态
if (c == 0) {//代表未被占用
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);//设置为独占
return true;
}
}
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;
}
tryAcquire 执行失败,则进入排队
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode); //构造一个新的节点
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
node.prev = pred;//当前队列尾节点作为当前节点的头结点
if (compareAndSetTail(pred, node)) {//cas 方式设置当前节点作为队列尾节点
pred.next = node; //设置之前的尾节点的后继节点为当前节点。
return node;
}
}
enq(node);//当前队列不存在,需要初始化。
return node;
}
队列初始化过程
private Node enq(final Node node) {
for (;;) { //这里多说一句 for这种用法和while(true)一致
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))// 各种cas操作
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {//如果有一个节点,直接把当前节点作为尾节点
t.next = node;
return t;
}
}
}
}
tryAcquire() 和 addWaiter()获取资源失败,放入队列尾部,则进行下一步:
自旋一直try
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true; //标识位 默认是true是代表拿到了资源
try {
boolean interrupted = false; //代表是否被中断过,这个在parkAndCheckInterrupt里面说比较合适
for (;;) {//又自旋
final Node p = node.predecessor();//前驱节点
//只有前驱是头结点了,他才能参与抢资源,不是老二就都没有资格而且只有当前头结点释放资源了或者被中断了他才能抢到。。
if (p == head && tryAcquire(arg)) {//自旋一直去尝试获取资源
setHead(node);//获取到了则将
p.next = null; // help GC 方便GC这个不用说了
failed = false;
return interrupted;
}
//进入waiting状态,直到被中断或者被调用unpark,详情看下面的分析
if (shouldParkAfterFailedAcquire(p, node) && //
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
Node.SIGNAL:waitStatus值,指示后续线程需要取消waiting(也就是unpark或者中断)
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus; //前驱节点的状态
if (ws == Node.SIGNAL) //是前驱节点就返回true
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
if (ws > 0) {//说明前驱节点已经被取消了,
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do {
node.prev = pred = pred.prev; //然后就跳过当前无效节点去找有效的。
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
* waitStatus must be 0 or PROPAGATE. Indicate that we
* need a signal, but don't park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);//如果有效设置前驱节点的状态为SIGNAL
}
return false;
}
设置当前线程为等待状态且看线程是否被中断过,并清除中断标识。
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
总结:
- 调用tryAcquire() 尝试直接去获取资源,如果成功则直接返回;
- 没成功,则addWaiter() 将该线程加入等待队列的尾部,并标记为独占模式;
- acquireQueued()让线程在等待队列中处于waiting状态,当轮到当前线程,也就是当前成为头结点的后继节点,则会被unpark())会去尝试获取资源。获取到资源后才返回。如果在整个等待过程中被中断过,则返回true,否则返回false。
- 如果线程在等待过程中被中断过,哪怕一次,也是不会干活的,会在抢到资源后进行selfInterrupt()自己中断自己,有点后置。
四、核心release说明(此部分相当于ReentrantLock.unlock()的过程)
release相当于acquire的相反操作,也就是释放资源,释放指定量的资源。
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;
}
tryRelease是ReentrantLock中的实现
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);
return free;
}
唤醒等待队列中下一个线程
private void unparkSuccessor(Node node) {
/*
* If status is negative (i.e., possibly needing signal) try
* to clear in anticipation of signalling. It is OK if this
* fails or if status is changed by waiting thread.
*/
int ws = node.waitStatus;
if (ws < 0)//如果状态为负(即可能需要信号),则尝试在预期信号时清除。如果失败或者状态被等待线程更改,这是正常的。
compareAndSetWaitStatus(node, ws, 0);//head节点状态设置成0
/*
* Thread to unpark is held in successor, which is normally
* just the next node. But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
*/
//unpark的线程保存在后续节点中,后者通常只是下一个节点。但如果被取消或明显为空,则从尾部向后移动以找到实际的未取消的继承者。
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);
}
总结:
只有(state=0)才会将资源彻底释放。
参考资料
线程状态说明 1
线程状态说明 2