概述:AQS最核心的数据结构就三个:同步队列、等待队列、同步状态。
public abstract class AbstractQueuedSynchronizer{
/**
* 1.【同步队列】的头节点
*/
private transient volatile Node head;
/**
* 2.【同步队列】的尾节点
*/
private transient volatile Node tail;
/**
* 3.【同步状态】
*/
private volatile int state;
/**
* 4.【等待队列】
*/
public class ConditionObject implements Condition, java.io.Serializable {
/** 等待队列的头节点 */
private transient Node firstWaiter;
/** 等待队列的尾节点 */
private transient Node lastWaiter;
}
}
两个队列中的节点都是通过AQS中内部类Node来实现的。主要字段:
waitStatus
当前节点的状态,具体看源码列出的注释。很重要,之后会在源码中讲解。
Node prev
同步队列节点指向的前置节点
Node next
同步队列节点指向的后置节点
Node nextWaiter
等待队列中节点指向的后置节点
Thread thread
当前节点持有的线程
static final class Node {
/** */
static final Node SHARED = new Node();
/** */
static final Node EXCLUSIVE = null;
/** 1.1 标明当前节点线程取消排队 */
static final int CANCELLED = 1;
/** 1.2 标明该节点的后置节点需要自己去唤醒 */
static final int SIGNAL = -1;
/** 1.3 标明当前节点在等待某个条件,此时节点在等待队列中 */
static final int CONDITION = -2;
/**
* waitStatus value to indicate the next acquireShared should
* unconditionally propagate
*/
static final int PROPAGATE = -3;
/**
* 2. 【等待状态】,值对于上面的四个常量
*/
volatile int waitStatus;
/**
* 3. 【同步队列】节点指向的前置节点
*/
volatile Node prev;
/**
* 4. 【同步队列】节点指向的后置节点
*/
volatile Node next;
/**
* 5. 【当前节点持有的线程】
*/
volatile Thread thread;
/**
* 6. 【等待队列】中节点指向的后置节点
*/
Node nextWaiter;
...
...
}
public class AQSDemo {
public static void main(String[] args) {
//创建所对象lock
ReentrantLock lock = new ReentrantLock();
//A顾客就是第一个顾客,此时受理窗口没有任何人,A可以直接去办理
new Thread(() -> {
lock.lock();
try{
System.out.println("-----A thread come in");
try { TimeUnit.MINUTES.sleep(20); }catch (Exception e) {e.printStackTrace();}
}finally {
lock.unlock();
}
},"A").start();
//第二个顾客,第二个线程---》由于受理业务的窗口只有一个(只能一个线程持有锁),此时B只能等待,
//进入候客区
new Thread(() -> {
lock.lock();
try{
System.out.println("-----B thread come in");
}finally {
lock.unlock();
}
},"B").start();
//第三个顾客,第三个线程---》由于受理业务的窗口只有一个(只能一个线程持有锁),此时C只能等待,
//进入候客区
new Thread(() -> {
lock.lock();
try{
System.out.println("-----C thread come in");
}finally {
lock.unlock();
}
},"C").start();
}
}
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
帮助理解:tryAcquire实现的具体加锁逻辑,当加锁失败时返回false,则会执行addWaiter(Node.EXCLUSIVE),将线程加入到同步队列中。Node.EXCLUSIVE为独占锁的模式,即同时只能有一个线程获取锁去执行。
这个方法有两个逻辑,首先如果该节点的前置节点是head会走第一个if,再次去尝试获取锁。
逻辑1:获取锁成功,则将头节点设置为自己,并返回到acquire方法,此时acquire方法执行完,代表获取锁成功,线程可以执行自己的逻辑了。这里有下面几个注意点:
逻辑2:获取锁失败或者前置节点不是头节点,都会走第二个if逻辑,首先会判断当前线程是否需要挂起,如果需要则执行线程挂起。
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
//获取当前节点的前置节点
final Node p = node.predecessor();
//前置节点是头结点,就尝试获取锁
if (p == head && tryAcquire(arg)) {//获取锁成功就可以进入if内部
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
//获取锁失败 或者 前置节点不是头结点
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())//线程被挂起阻塞
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
private void setHead(Node node) {
head = node;
node.thread = null;
node.prev = null;
}
帮助理解:把当前节点的前置节点的状态修改为Node.SIGNAL(指明让谁来把自己唤醒)
1、前言:
2、概述:
4、该方法的内部内容:
5、查看源码:
//pred:当前节点的前置节点
//node:当前节点
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
//前置节点已经是-1,直接返回true。就可以安心挂起了
if (ws == Node.SIGNAL)
return true;
if (ws > 0) {//标识前置节点获取锁的请求取消了。那么就得再往前找,找到一个没有放弃获取锁请求的节点
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
//前置节点不是-1,那么修改为Node.SIGNAL
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
1、概述:
2、查看源码:
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);//挂起线程
return Thread.interrupted();
}
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;//获得head节点的状态
if (ws < 0){
compareAndSetWaitStatus(node, ws, 0);// 设置head节点状态为0
}
Node s = node.next;//得到head节点的下一个节点
if (s == null || s.waitStatus > 0) { //如果下一个节点为null或者status>0表示cancelled状态.
//通过从尾部节点开始扫描,找到距离head最近的一个waitStatus<=0的节点
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null) //next节点不为空,直接唤醒这个线程即可(总之离头节点最近的一个可唤醒节点)
LockSupport.unpark(s.thread);
}
②. 当A线程办理好业务,离开的时候,会把傀儡结点的waitStatus从-1改为0 | 将status从1改为0,将当前线程置为null
③. 这个时候如果B上位,首先将status从0改为1(表示占用),把thread置为线程B ,会执行如下图的①②③④,会触发GC,然后就把第一个灰色的傀儡结点给清除掉了,这个时候原来的B结点重新成为傀儡结点。