1) 同步队列
同步器依赖内部的同步队列(一个FIFO双向队列)来完成同步状态的管理,当前线程获取同步状态失败时,同步器会将当前线程以及等待状态等信息构造成为一个节点(Node)并将其加入同步队列,同时会阻塞当前线程,当同步状态释放时,会把首节点中的线程唤醒,使其再次尝试获取同步状态。
下面直接呈上源代码!!!字多看的头疼
同步队列中的节点(Node):
static final class Node {
/** waitStatus value to indicate 线程被中断或者超时 */
static final int CANCELLED = 1;
/** waitStatus value to 按时后继节点可以运行 */
static final int SIGNAL = -1;
/** waitStatus value to 节点在等待队列(WAIT),等待被唤醒后加入同步队列(BLOCK) */
static final int CONDITION = -2;
/**
* waitStatus value to indicate the next acquireShared should
* unconditionally propagate
*/
static final int PROPAGATE = -3;
// 等待状态如上所示
volatile int waitStatus;
// 前驱节点
volatile Node prev;
// 后继
volatile Node next;
// 获取同步状态的线程
volatile Thread thread;
// 等待队列中的后继节点
Node nextWaiter;
final boolean isShared() {
return nextWaiter == SHARED;
}
// 获取节点的前继
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,转存同步队列,由于加入过程需要是线程安全,所以加入CAS操作来设置尾节点,代码如下:
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// tail为同步队列的尾节点
Node pred = tail;
if (pred != null) {
node.prev = pred;
// 快速尝试在尾部添加,如果CAS不存在,使用死循环的方式进行添加节点
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
// 如果尾结点为null
enq(node);
return node;
}
private Node enq(final Node node) {
// “死循环”来保证节点的正确添加,
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
过程见下图:
同步队列遵循FIFO,首节点是获取同步状态成功的节点,首节点的线程在释放同步状态时,将会唤醒后继节点,而后继节点将会在获取同步状态成功时将自己设置为首节点。
private void unparkSuccessor(Node node) {
/*
获取节点状态
*/
int ws = node.waitStatus;
// 如果小于0,为头节点状态设置初始值
if (ws < 0)
compareAndSetWaitStatus(node, ws, 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);
}
2) 独占同步状态的获取与释放
-
获取
通过调用同步器的
acquire(int arg)
方法可以获取同步状态,该方法对中断不敏感,也就是由于线程获取同步状态失败后进入同步队列中,后续对线程进行中断操作时,线程不会从同步队列中移出代码:
public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); } 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; } // 查看是否被中断,中断后仍然在同步队列中保存。这也是为什么不能直接使用头节点的后继的原因 了 if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) cancelAcquire(node); } }
流程:
- 调用自定义同步器实现的
tryAcquire(int arg)
方法,同步状态获取失败执行(2)。 - 构造同步节点(设置独占式
Node.EXCLUSIVE
)并通过addWaiter(Node node)
方法将该节点加入到同步队列的尾部。 - 调用
acquireQueued(Node node,int arg)
方法,使得该节点以“死循环”的方式获取同步状态。
-
释放
当前线程获取同步状态并执行了相应逻辑之后,就需要释放同步状态。通过调用同步器的
release(int arg)
方法可以释放同步状态,该方法在释放了同步状态之后,会唤醒其后继节点。代码:
public final boolean release(int arg) { if (tryRelease(arg)) { Node h = head; // 唤醒后继节点 if (h != null && h.waitStatus != 0) unparkSuccessor(h); return true; } return false; }