文章目录
1 介绍
在分析 Java 并发包 java.util.concurrent 源码的时候,少不了需要了解 AbstractQueuedSynchronizer(以下简写AQS)这个抽象类,因为它是 Java 并发包的基础工具类,是实现 ReentrantLock、CountDownLatch、Semaphore、FutureTask 等类的基础。
Google 一下 AbstractQueuedSynchronizer,我们可以找到很多关于 AQS 的介绍,但是很多都没有介绍清楚,因为大部分文章没有把其中的一些关键的细节说清楚。
本文将从 ReentrantLock 的公平锁源码出发,分析下 AbstractQueuedSynchronizer 这个类是怎么工作的,希望能给大家提供一些简单的帮助。
2 AQS 结构和内部类node介绍
2.1 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;
/**
* 锁的状态
* 这个是最重要的,不过也是最简单的,代表当前锁的状态,0代表没有被占用,大于0代表有线程持有当前锁
* 之所以说大于0,而不是等于1,是因为锁可以重入嘛,每次重入都加上1
*/
private volatile int state;
/**
* The current owner of exclusive mode synchronization.
* 这个是继承来的。相当于就是标记占用锁的线程。
* 也就是代表当前持有独占锁的线程,举个最重要的使用例子,因为锁可以重入
* reentrantLock.lock()可以嵌套调用多次,所以每次用这个来判断当前线程是否已经拥有了锁
* if (currentThread == getExclusiveOwnerThread()) {state++}
*/
private transient Thread exclusiveOwnerThread;
还是比较简单的,毕竟也就四个属性。重点是里面的node节点(每个线程对应一个节点)。
AbstractQueuedSynchronizer的阻塞队列示意如下所示,但是要注意了,之后分析过程中的阻塞队列不包含 head,不包含 head,不包含 head(重要的事情说仨遍)。并且head节点代表就是获取到锁的线程节点。
阻塞队列结构就是由node节点组成的双向链表。一个线程包装成一个node节点。
2.2 node节点结构
前面说了每个节点中独有包装一个线程。以及上下节点的关联。来组成双向链表来形成一个阻塞队列。这里要注意后面还会有一个条件队列也是由node节点组成,但是他是一个单向链表,node节点结构如下所示
static final class Node {
/** 模式标记,分为共享与独占 */
// 共享
static final Node SHARED = new Node();
// 私有 独占
static final Node EXCLUSIVE = null;
/**
* waitStatus的值 看下面的翻译有说明
* CANCELLED =1:代码此线程取消了争抢这个锁
* SIGNAL=-1:表示当前node的后继节点对应的线程需要被唤醒 这个状态是当有后继节点加进来的去设置的默认是0
* 也就是说如果当前节点是尾节点的话 一般他的状态就是0
* CONDITION =-2:表示当前node就是条件队列中这个后面会讲。暂时用不到先不管
* PROPAGATE =-3:同样的不分析,略过吧
* */
static final int CANCELLED = 1;
static final int SIGNAL = -1;
static final int CONDITION = -2;
static final int PROPAGATE = -3;
/**
* Status field, taking on only the values:
* SIGNAL: The successor of this node is (or will soon be)
* blocked (via park), so the current node must
* unpark its successor when it releases or
* cancels. To avoid races, acquire methods must
* first indicate they need a signal,
* then retry the atomic acquire, and then,
* on failure, block.
* CANCELLED: This node is cancelled due to timeout or interrupt.
* Nodes never leave this state. In particular,
* a thread with cancelled node never again blocks.
* CONDITION: This node is currently on a condition queue.
* It will not be used as a sync queue node
* until transferred, at which time the status
* will be set to 0. (Use of this value here has
* nothing to do with the other uses of the
* field, but simplifies mechanics.)
* PROPAGATE: A releaseShared should be propagated to other
* nodes. This is set (for head node only) in
* doReleaseShared to ensure propagation
* continues, even if other operations have
* since intervened.
* 0: None of the above
*
* The values are arranged numerically to simplify use.
* Non-negative values mean that a node doesn't need to
* signal. So, most code doesn't need to check for particular
* values, just for sign.
*
* The field is initialized to 0 for normal sync nodes, and
* CONDITION for condition nodes. It is modified using CAS
* (or when possible, unconditional volatile writes).
* 这个就是代表状态 取值范围就是上面的几种情况
*/
volatile int waitStatus;
/**
* Link to predecessor node that current node/thread relies on
* for checking waitStatus. Assigned during enqueuing, and nulled
* out (for sake of GC) only upon dequeuing. Also, upon
* cancellation of a predecessor, we short-circuit while
* finding a non-cancelled one, which will always exist
* because the head node is never cancelled: A node becomes
* head only as a result of successful acquire. A
* cancelled thread never succeeds in acquiring, and a thread only
* cancels itself, not any other node.
*/
// 上一个节点 和next节点组成双向链表的阻塞队列
volatile Node prev;
/**
* Link to the successor node that the current node/thread
* unparks upon release. Assigned during enqueuing, adjusted
* when bypassing cancelled predecessors, and nulled out (for
* sake of GC) when dequeued. The enq operation does not
* assign next field of a predecessor until after attachment,
* so seeing a null next field does not necessarily mean that
* node is at end of queue. However, if a next field appears
* to be null, we can scan prev's from the tail to
* double-check. The next field of cancelled nodes is set to
* point to the node itself instead of null, to make life
* easier for isOnSyncQueue.
*/
// 下一个节点 和prev节点组成双向链表的阻塞队列
volatile Node next;
/**
* The thread that enqueued this node. Initialized on
* construction and nulled out after use.
*/
// 当前节点对应的线程 这里就是上面说的节点包装的线程
volatile Thread thread;
/**
* Link to next node waiting on condition, or the special
* value SHARED. Because condition queues are accessed only
* when holding in exclusive mode, we just need a simple
* linked queue to hold nodes while they are waiting on
* conditions. They are then transferred to the queue to
* re-acquire. And because conditions can only be exclusive,
* we save a field by using special value to indicate shared
* mode.
*/
// 下一个等待的节点 这个是条件对列的 这是一个单向链表
Node nextWaiter;
/**
* Returns true if node is waiting in shared mode.
*/
// 判断是不是共享模式
final boolean isShared() {
return nextWaiter == SHARED;
}
/**
* Returns previous node, or throws NullPointerException if null.
* Use when predecessor cannot be null. The null check could
* be elided, but is present to help the VM.
*
* @return the predecessor of this node
*/
// 获取前一个节点(前驱节点)
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
// 构造方法
Node() { // Used to establish initial head or SHARED marker
}
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;
}
}
Node 的属性结构也不难,就四个属性thread + waitStatus + prev + next,但是大家心里一定要有这个概念。同时它的几种状态大家要清楚。
因为后面会多次用到,心里要时刻记着它们,心里想着上面阻塞队列的结构图就可以了。下面,我们开始说 ReentrantLock 的公平锁。
2.3 ReentrantLock的使用方式
public class OrderService {
// 这里是创建
// 注意构造方法可以传一个布尔的参数。它是用来创建不同的锁用的。默认是创建非公平锁。
// 当传入true时就是创建一个公平锁。
// 公平锁和非公平锁后面会说明的 这里暂时先不管
private ReentrantLock reentrantLock = new ReentrantLock(true);
public void placeOrder() {
// 比如我们要在同一时间,只允许一个线程创建订单的话
reentrantLock.lock();
// 通常,lock 之后紧跟着 try 语句。并且reentrantLock.lock();不要放在try里面。那样有可能会无辜释放锁。
// 因为如果lock()方法中没有获取到锁,但是却发生异常。finally就会释放。可能会出现问题
try {
// 这块代码只能有一个线程进来(获取到锁的线程),
// 其他的线程在lock()方法上阻塞,等待获取到锁,再进来
// 执行代码...
} finally {
// 释放锁
reentrantLock.unlock();
}
}
}
2.3.1 ReentrantLock内部Sync实现
到这不熟悉源码的同学可能会觉得创建ReentrantLock和AQS有什么联系吗?并没有创建AQS呀。其实在ReentrantLock 的内部用了内部类 Sync 来管理锁,而Sync又是继承自AQS,所以真正的获取锁和释放锁是由 Sync 的实现类来控制的。
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;
/**
* Performs {@link Lock#lock}. The main reason for subclassing
* is to allow fast path for nonfair version.
*/
abstract void lock();
/**
* Performs non-fair tryLock. tryAcquire is implemented in
* subclasses, but both need nonfair try for trylock method.
*
* 尝试获取锁
*
* 这个是非公平的方式去获取锁
*
*/
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// compareAndSetState这个判断是否获取到锁
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;
}
// 这释放锁的操作 这里看传入的参数 可以直接完全释放
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;
}
}
而前面说的公平锁和非公平锁又是继承自Sync来实现获取和释放锁的
2.3.2 ReentrantLock内部公平锁(FairSync )实现
公平锁
/**
* 公平锁的实现
* Sync object for fair locks
*
*
* 公平锁和非公平锁的区别就是 这个就是有没有判断队列是否有等待的线程
*
* 公平锁是先去查看对列中是否有等待的线程,有的话会遵循链表顺序去执行,反之直接执行(因为没有等待的线程直接执行)
* 非公平是直接去尝试获取锁 获取去到就执行 没获取到也加入队列
*/
static final class FairSync extends ReentrantLock.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) {
// hasQueuedPredecessors这个是判断队列中是否还有其他的线程在等待
// compareAndSetState这个判断是否获取到锁
// 和非公平锁的不同点 (非公平锁直接尝试获取锁,公平锁先查看阻塞队列是否有阻塞的线程)
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;
}
}
2.3.3 ReentrantLock内部非公平锁(NonfairSync)实现
非公平锁
/**
* 非公平锁
* Sync object for non-fair locks
*
* 公平锁和非公平锁的区别就是 这个就是有没有判断队列是否有等待的线程
*
* 公平锁是先去查看对列中是否有等待的线程,有的话会遵循链表顺序去执行,反之直接执行(因为没有等待的线程直接执行)
* 非公平是直接去尝试获取锁 获取去到就执行 没获取到也加入队列
*/
static final class NonfairSync extends ReentrantLock.Sync {
private static final long serialVersionUID = 7316153563782823691L;
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
3 获取锁和释放锁
3.1 线程获取锁
这里以公平锁获取为例。ps 为了方便阅读有些方法我放在下面一起了 (比如acquire(int arg)其实是来自父类AQS中的)
/**
* 公平锁的实现
* Sync object for fair locks
*
*
* 公平锁和非公平锁的区别就是 这个就是有没有判断队列是否有等待的线程
*
* 公平锁是先去查看对列中是否有等待的线程,有的话会遵循链表顺序去执行,
* 反之直接执行(因为没有等待的线程直接执行)
* 非公平是直接去尝试获取锁 获取去到就执行 没获取到也加入队列
*/
static final class FairSync extends ReentrantLock.Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
/**
* 这个方法是继承自AQS中的方法
*/
public final void acquire(int arg) {
// tryAcquire(arg)是尝试获取锁 获取成功返回true 反之false
// 获取成功就一直往下执行 我们的业务代码 不会再执行acquireQueued()方法了
// 获取失败后,执行addWaiter和acquireQueued方法将当前线程封装成node对象
// 并放入等待链表中等待获取锁 也就是进到阻塞队列的最后
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
/**
* 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) {
// hasQueuedPredecessors这个是判断队列中是否还有其他的线程在等待
// compareAndSetState这个判断是否获取到锁
// 和非公平锁的不同点 (非公平锁直接尝试获取锁,公平锁先查看阻塞队列是否有阻塞的线程)
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;
}
}
// 和enq意思差不多 enq就是有一个初始化的过程和不成功就是循环执行(成功为止)
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;
// 将node设置成尾节点
if (compareAndSetTail(pred, node)) {
// 进到这里说明设置成功,当前node==tail, 将自己与之前的队尾相连,
// 上面已经有 node.prev = pred
// 加上下面这句,也就实现了和之前的尾节点双向连接了
pred.next = node;
return node;
}
}
// 仔细看看上面的代码,如果会到这里,
// 说明 pred==null(队列是空的) 或者 CAS失败(有线程在竞争入队compareAndSetTail执行失败)
enq(node);
return node;
}
/**
* Acquires in exclusive uninterruptible mode for thread already in
* queue. Used by condition wait methods as well as acquire.
*
* @param node the node
* @param arg the acquire argument
* @return {@code true} if interrupted while waiting
* 这个方法要么获取到方法 要么就放入阻塞线程
*/
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)) {
// 将当前节点设置成头结点
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
// 到这里,说明上面没有执行成功,或者当前node不是队头,
// 要么就是tryAcquire(arg)没有抢赢别人,继续往下看
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
/**
* Checks and updates status for a node that failed to acquire.
* Returns true if thread should block. This is the main signal
* control in all acquire loops. Requires that pred == node.prev.
*
* 说明当前线程没有抢到锁,是否需要挂起当前线程?
*
* 注意这里是再循环体里面
* 注意这里是再循环体里面
* 注意这里是再循环体里面
*
* @param pred node's predecessor holding status
* @param node the node
* @return {@code true} if thread should block
* 注意这里是再循环体里面
* 第一次进来可能会执行else里面的代码,将上一个节点的状态设置成-1
* 第二次执行就会执行第一个if语句里面的。然后返回ture
*/
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
/*
* 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
*
*
* 这里是跳过前驱节点状态 大于0的节点
*
* 往上遍历查找前驱节点
* 前驱节点 waitStatus大于0 ,之前说过,大于0 说明前驱节点取消了排队。这里需要知道这点:
* 进入阻塞队列排队的线程会被挂起,而唤醒的操作是由前驱节点完成的。
* 所以下面这块代码说的是将当前节点的prev指向waitStatus<=0的节点,
* 简单说,就是为了找个好爹,因为你还得依赖它来唤醒呢,如果前驱节点取消了排队,
* 找前驱节点的前驱节点做爹,往前循环总能找到一个好爹的
*
*/
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.
*
* 仔细想想,如果进入到这个分支意味着什么
* 前驱节点的waitStatus不等于-1和1,那也就是只可能是0,-2,-3
* 在我们前面的源码中,都没有看到有设置waitStatus的,所以每个新的node入队时,waitStatu都是0
* 用CAS将前驱节点的waitStatus设置为Node.SIGNAL(也就是-1)
* 这里就是将赏你个节点的状态改为-1
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
/**
* Convenience method to park and then check if
*
* 挂起当前线程 并检查是否已经中断
* 这里用了LockSupport.park(this)来挂起线程,然后就停在这里了,等待被唤醒=======
* @return {@code true} if interrupted
*/
private final boolean parkAndCheckInterrupt() {
// 挂起线程等待被唤醒 执行到这线程就停在这里了 等待被唤醒
LockSupport.park(this);
// 检查是否已经中断
return Thread.interrupted();
}
到这获取锁也就差不多说明白了。获取到了就继续执行,内有获取到就挂起暂停等被唤醒。不明白的可以多看几遍
3.2 线程释放锁
最后介绍下唤醒的动作了
// 唤醒的代码还是比较简单的,你如果上面加锁的都看懂了,下面都不需要看就知道怎么回事了
// 解锁一般都是解锁一次 因为有重入的可能每次只解锁一次。特殊需求除外也可以传入具体的次数
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) {
int c = getState() - releases;
//判断线程是不是同一个 不是则有问题了
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
// 是否完全释放锁
boolean free = false;
//其实就是重入的问题,如果c==0,也就是说没有嵌套锁了,可以释放了,否则还不能释放掉
if (c == 0) {
free = true;
//设置占用锁的线程为空。也就是没有线程占用锁
setExclusiveOwnerThread(null);
}
// 没有完全释放锁 需要将锁释放c次 这里是重入的设计
setState(c);
return free;
}
/**
1. Wakes up node's successor, if one exists.
2. 3. @param node the node
*/
// 唤醒后继节点
// 从上面调用处知道,参数node是head头结点
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;
// 如果head节点当前waitStatus<0, 将其修改为0
if (ws < 0)
compareAndSetWaitStatus(node, ws, 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.
*/
// 下面的代码就是唤醒后继节点,但是有可能后继节点取消了等待(waitStatus==1)
// 从队尾往前找,找到waitStatus<=0的所有节点中排在最前面的
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
// 从后往前找,仔细看代码,不必担心中间有节点取消(waitStatus==1)的情况
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
// 唤醒线程
LockSupport.unpark(s.thread);
}
唤醒下一个节点的线程以后,被唤醒的线程将从以下继续往前走:
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this); // 刚刚线程被挂起在这里了
return Thread.interrupted();
}
说到这不知道大家有没有注意在解锁的时候。只是唤醒了。并没有将头结点的上一个节点取消关联。也就是唤醒后没有将上一个获取到锁的节点剔除在双向链表中
其实是有的只是并没有放在解锁这边而是放在acquireQueued(final Node node, int arg)方法中。就是 // help GC这句前面的还有在setHead(node)中
/**
* Acquires in exclusive uninterruptible mode for thread already in
* queue. Used by condition wait methods as well as acquire.
*
* @param node the node
* @param arg the acquire argument
* @return {@code true} if interrupted while waiting
*/
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)) {
// 将当前节点设置成头结点
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
// 到这里,说明上面没有执行成功,或者当前node不是队头,
// 要么就是tryAcquire(arg)没有抢赢别人,继续往下看
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
4 总结
在并发环境下,加锁和解锁需要以下三个部件的协调:
- 锁状态。怎么知道锁是不是被占用被占用了几次(重入问题state等于就是占用几次),这个就是 state 的作用,0 代表没有线程占有锁,线程就可以去争抢这个锁,用 CAS 将 state 设为 1,如果 CAS 成功,说明抢到了锁,这样其他线程就抢不到了,如果锁重入的话,state进行+1 就可以,解锁就是减 1,直到 state 又变为 0,代表释放锁,所以 lock() 和 unlock() 必须要配对啊。然后唤醒等待队列中的第一个线程,让其来占有锁。
- 线程的阻塞和解除阻塞。AQS 中采用了 LockSupport.park(thread) 来挂起线程,用 unpark 来唤醒线程。
- 阻塞队列。因为争抢锁的线程可能很多,AQS 用的是一个 FIFO 的队列,就是一个链表,每个 node 都持有后继节点的引用。