前言
jdk1.6之前synchronized性能一直較為低下;jdk1.6之后,java開發(fā)團(tuán)隊(duì)對(duì)synchronized進(jìn)行了大量的優(yōu)化,其性能也提升了很多。但是與Lock
系列鎖相比,缺少了獲取/釋放鎖的可操作性,可中斷和超時(shí)處理等特性。另外,它只支持獨(dú)占鎖,不支持共享鎖。
說到Lock
,不得不說其依賴的AQS
,它是非常非常重要的一個(gè)鎖抽象。
那么AQS又是什么呢?
AQS
,英文名為AbstractQueuedSynchronizer
,也叫隊(duì)列同步器,是構(gòu)建同步鎖或其他并發(fā)框架的基礎(chǔ),Doug Lea希望它能夠成為實(shí)現(xiàn)大部分同步需求的基礎(chǔ)和抽象。AQS
解決了鎖實(shí)現(xiàn)的大部分細(xì)節(jié),如同步狀態(tài)的獲取、FIFO隊(duì)列。
AQS的使用主要是通過繼承的方式,然后繼承者被同步器所組合依賴。
主要方法
int getState() // 返回同步狀態(tài)的當(dāng)前值
void setState(int newState) // 設(shè)置同步狀態(tài)
boolean compareAndSetState(int expect, int update) // 使用CAS設(shè)置當(dāng)前同步狀態(tài)
void acquire(int arg) // 以獨(dú)占的方式獲取同步狀態(tài)
void acquireInterruptibly(int arg) throws InterruptedException // 以獨(dú)占的方式獲取同步狀態(tài)(可中斷)
void acquireShared(int arg) // 以共享的方式獲取同步狀態(tài)
void acquireSharedInterruptibly(int arg) throws InterruptedException // 以共享的方式獲取同步狀態(tài)(可中斷)
int tryAcquireShared(int arg) // 以共享的方式獲取同步狀態(tài)(非阻塞,需要繼承者實(shí)現(xiàn))
boolean tryAcquireSharedNanos(int arg, long nanosTimeout) throws InterruptedException // 以共享的方式獲取同步狀態(tài)(帶超時(shí))
boolean isHeldExclusively() // 當(dāng)前線程是否持有同步狀態(tài)
boolean release(int arg) // 釋放同步狀態(tài)(獨(dú)占式)
boolean releaseShared(int arg) // 釋放同步狀態(tài)(共享式)
boolean tryAcquire(int arg) // 以獨(dú)占的方式獲取同步狀態(tài)(非阻塞,需要繼承者實(shí)現(xiàn))
boolean tryAcquireNanos(int arg, long nanosTimeout) throws InterruptedException // 以獨(dú)占的方式獲取同步狀態(tài)(帶超時(shí))
boolean tryRelease(int arg) // 以獨(dú)占的方式釋放同步狀態(tài)(非阻塞,需要繼承者實(shí)現(xiàn))
boolean tryReleaseShared(int arg) // 以共享的方式釋放同步狀態(tài)(非阻塞,需要繼承者實(shí)現(xiàn))
CLH隊(duì)列
CLH是一個(gè)FIFO(先進(jìn)先出的隊(duì)列),AQS依賴其來完成同步狀態(tài)的管理。當(dāng)前線程如果獲取同步狀態(tài)失敗,AQS會(huì)將當(dāng)前線程及等待狀態(tài)等組裝成一個(gè)Node,并加入到CLH隊(duì)列,同時(shí)會(huì)阻塞當(dāng)前線程,當(dāng)同步狀態(tài)釋放時(shí),會(huì)將首節(jié)點(diǎn)喚醒,并使其重新嘗試獲取同步狀態(tài)。
我們來看一下CLH中的一個(gè)Node的信息有哪些
static final class Node {
// 標(biāo)識(shí)節(jié)點(diǎn)是否共享mode
static final Node SHARED = new Node();
// 標(biāo)識(shí)節(jié)點(diǎn)是否獨(dú)占mode
static final Node EXCLUSIVE = null;
/** 取消狀態(tài),線程被中斷 */
static final int CANCELLED = 1;
/** 通知狀態(tài),繼任節(jié)點(diǎn)需要被喚醒 */
static final int SIGNAL = -1;
/** 帶條件登臺(tái)狀態(tài) */
static final int CONDITION = -2;
/** 下一次獲取同步狀態(tài)(共享)應(yīng)該無條件傳播 */
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.
*/
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.
*/
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;
}
}
CLH同步隊(duì)列結(jié)構(gòu)圖
入隊(duì)列
我們看看AQS中入隊(duì)列的代碼
private Node addWaiter(Node mode) {
// 新建一個(gè)節(jié)點(diǎn),mode可以是獨(dú)占式,也可以是共享式
Node node = new Node(Thread.currentThread(), mode);
// 下面這段代碼會(huì)嘗試添加一個(gè)尾節(jié)點(diǎn)
// 1. 將舊的tail節(jié)點(diǎn)賦值為pred
// 2. 舊的tail尾節(jié)點(diǎn)不為null,則new節(jié)點(diǎn)的prev指向舊的tail節(jié)點(diǎn)
// 3. 使用CAS設(shè)置new節(jié)點(diǎn)為tail節(jié)點(diǎn)
// 4. 如果設(shè)置新節(jié)點(diǎn)為tail節(jié)點(diǎn)成功之后,則將舊的tail節(jié)點(diǎn)的next指向new節(jié)點(diǎn)
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
// 在tail節(jié)點(diǎn)為null或者將新節(jié)點(diǎn)設(shè)置為tail節(jié)點(diǎn)失敗時(shí),通過自旋的方式再次設(shè)置tail節(jié)點(diǎn)。
// 這兒需要特別注意,為什么設(shè)置tail節(jié)點(diǎn)會(huì)有失敗的可能,因?yàn)閍ddWaiter方法可能會(huì)同時(shí)被多個(gè)線程調(diào)用
enq(node);
return node;
}
在初次嘗試如隊(duì)列失敗后,會(huì)調(diào)用enq
方法,我們進(jìn)一步分析此方法的代碼
private Node enq(final Node node) {
// for死循環(huán),其實(shí)就是自旋操作
for (;;) {
Node t = tail;
// tail == null表示是第一次添加節(jié)點(diǎn)
if (t == null) { // Must initialize
// 1. CAS設(shè)置head節(jié)點(diǎn)可能會(huì)失敗。如果失敗,表示其他線程已經(jīng)設(shè)置head節(jié)點(diǎn)成功了
// 2. 這兒設(shè)置了一個(gè)空的head節(jié)點(diǎn),后面我們會(huì)分析一下為啥需要設(shè)置一個(gè)空的head節(jié)點(diǎn)
// 3. 在設(shè)置head節(jié)點(diǎn)成功之后,仍然需要再次執(zhí)行下面的else邏輯進(jìn)行尾節(jié)點(diǎn)的設(shè)置
if (compareAndSetHead(new Node()))
tail = head;
} else {
// 這兒其實(shí)和AddWaiter的邏輯是一樣的,通過CAS設(shè)置尾節(jié)點(diǎn)
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
如隊(duì)列的整個(gè)過程圖如下:
同步狀態(tài)獲取與釋放
AQS內(nèi)部使用了模板方法模式
來設(shè)計(jì),子類通過繼承的方式實(shí)現(xiàn)抽象方法
(不完全是抽象方法,其實(shí)是一個(gè)普通的方法,父類默認(rèn)實(shí)現(xiàn)為throwException的方式)。主要的抽象方法有下面幾個(gè)
- 獨(dú)占式獲取同步狀態(tài)
- 獨(dú)占式釋放同步狀態(tài)
- 共享式獲取同步狀態(tài)
- 共享式釋放同步狀態(tài)
- 當(dāng)前線程是否獲取到了同步狀態(tài)
獨(dú)占式獲取
什么叫獨(dú)占式呢?其實(shí)就是同一時(shí)刻至多只能有一個(gè)線程獲取到同步狀態(tài)。
我們看看獨(dú)占式獲取同步狀態(tài)的代碼,acquire(int arg)
:
public final void acquire(int arg) {
// 1. 如果沒有獲取到同步狀態(tài),則新創(chuàng)建一個(gè)獨(dú)占式節(jié)點(diǎn),并將其添加到同步隊(duì)列。
// 2. 否則認(rèn)為已經(jīng)獲取到同步狀態(tài)成功了,當(dāng)前線程就可以執(zhí)行自身的邏輯了。
// 3. 該方法不會(huì)響應(yīng)中斷,但是如果在執(zhí)行的過程中有過中斷操作,則會(huì)設(shè)置當(dāng)前線程的中斷狀態(tài)為true
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
我們前面已經(jīng)分析過了addWaiter
方法,下面我們分析一下acquireQueued
方法。
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false; // 是否有過中斷
// 這兒有自旋操作
for (;;) {
// 如果當(dāng)前節(jié)點(diǎn)的前任是head節(jié)點(diǎn),則嘗試獲取同步狀態(tài),若獲取成功,則執(zhí)行下面操作:
// 1. 設(shè)置當(dāng)前節(jié)點(diǎn)為head節(jié)點(diǎn)
// 2. 前任節(jié)點(diǎn)的next指向null,那么前任節(jié)點(diǎn)就沒有依賴存活的對(duì)象了,可以更快得到GC
// 3. 返回是否有過中斷的標(biāo)記
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
// 當(dāng)前任節(jié)點(diǎn)不是head節(jié)點(diǎn),或者獲取同步狀態(tài)失敗。則會(huì)通過`park操作`讓`當(dāng)前線程等待`,讓出CPU。我們后面會(huì)分析這兩個(gè)方法,這兒暫時(shí)跳過
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
// 如果失敗過,則會(huì)對(duì)當(dāng)前節(jié)點(diǎn)進(jìn)行同步狀態(tài)的取消獲取操作
if (failed)
cancelAcquire(node);
}
}
acquire方法的流程圖如下
我們接著看一下acquireInterruptibly方法,該方法在獲取同步狀態(tài)的時(shí)候,可以響應(yīng)中斷
public final void acquireInterruptibly(int arg) throws InterruptedException {
// 線程中斷了,直接拋出異常
if (Thread.interrupted())
throw new InterruptedException();
// 如果嘗試獲取同步狀態(tài)失敗,則執(zhí)行doAcquireInterruptibly方法
if (!tryAcquire(arg))
doAcquireInterruptibly(arg);
}
接著,我們看看doAcquireInterruptibly方法,該方法和acquireQueued
基本一樣,唯一的兩點(diǎn)差別如下:
- 方法聲明上添加了
throws InterruptedException
- 在park的時(shí)候,如果檢查到中斷,則拋出異常
throw new InterruptedException()
private void doAcquireInterruptibly(int arg) throws InterruptedException {
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
接著我們看看超時(shí)獲取同步狀態(tài)的方法,tryAcquireNanos
public final boolean tryAcquireNanos(int arg, long nanosTimeout) throws InterruptedException {
// 有過中斷,直接拋出異常
if (Thread.interrupted())
throw new InterruptedException();
// 嘗試獲取同步狀態(tài)失敗時(shí),則執(zhí)行方法doAcquireNanos
return tryAcquire(arg) ||
doAcquireNanos(arg, nanosTimeout);
}
我們接著分析一下doAcquireNanos
private boolean doAcquireNanos(int arg, long nanosTimeout) throws InterruptedException {
// 如果超時(shí)納秒數(shù)不是正數(shù),則直接返回未獲取到同步狀態(tài)
if (nanosTimeout <= 0L)
return false;
// 當(dāng)前時(shí)間+超時(shí)時(shí)間=獲取同步狀態(tài)的截止時(shí)間
final long deadline = System.nanoTime() + nanosTimeout;
// 創(chuàng)建一個(gè)獨(dú)占式的節(jié)點(diǎn)
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
// 自旋操作
for (;;) {
// 這兒和acquireQueued中相應(yīng)的代碼一模一樣
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return true;
}
// 這兒再次判斷是否超過截止時(shí)間
nanosTimeout = deadline - System.nanoTime();
if (nanosTimeout <= 0L)
return false;
// 和acquireQueued不同的地方有兩點(diǎn)
// 1. park方法為帶毫秒的阻塞(超過指定時(shí)間則不再wait,繼續(xù)執(zhí)行)
// 2. 如果離截止時(shí)間不超過1微妙,為了性能考慮,不再parkNanos了,而是for死循環(huán)。即使使用parkNanos也不準(zhǔn)確。spinForTimeoutThreshold為1000(1000納秒==1微秒)
if (shouldParkAfterFailedAcquire(p, node) &&
nanosTimeout > spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanosTimeout);
// 同中斷的方法一樣,超時(shí)也會(huì)響應(yīng)中斷
if (Thread.interrupted())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
整體流程圖如下:
獨(dú)占式釋放同步狀態(tài)方法只有一個(gè)——release
public final boolean release(int arg) {
// 嘗試釋放同步狀態(tài),若沒成功,則返回false
if (tryRelease(arg)) {
Node h = head;
// head節(jié)點(diǎn)不為null,且waitStatus不是初始狀態(tài),則unpark繼任節(jié)點(diǎn)
if (h != null && h.waitStatus != 0)
// 這兒會(huì)喚醒繼任節(jié)點(diǎn)
unparkSuccessor(h);
return true;
}
return false;
}
共享式
我們接著分析共享式獲取同步狀態(tài)方法
public final void acquireShared(int arg) {
// tryAcquireShared返回值>=0,表示獲取成功了,否則需要自旋地獲取
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
private void doAcquireShared(int arg) {
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
// 自旋操作
for (;;) {
final Node p = node.predecessor();
// 前任節(jié)點(diǎn)為head節(jié)點(diǎn),嘗試獲取共享的同步狀態(tài)
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
// 前任節(jié)點(diǎn)不是head節(jié)點(diǎn),需要park操作
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
阻塞和喚醒線程
不管是共享式,還是獨(dú)占式,都會(huì)調(diào)用下面的邏輯,我們需要分析其中關(guān)鍵的兩個(gè)方法
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
// 前驅(qū)節(jié)點(diǎn)為SIGNAL,表示需要等待,直接返回true
if (ws == Node.SIGNAL)
return true;
// 前驅(qū)節(jié)點(diǎn)的狀態(tài)>0,表示為CANCELED,表示為已中斷的線程,需要從同步隊(duì)列中移除
if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
// 前驅(qū)節(jié)點(diǎn)狀態(tài)不為SINGNAL和CANCELED(則為CONDITION和PROPAGATE),則需要將前驅(qū)節(jié)點(diǎn)的狀態(tài)設(shè)置為SIGNAL
} else {
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
接著我們分析一下方法parkAndCheckInterrupt
,很簡(jiǎn)單,直接通過park
方式使當(dāng)前線程變?yōu)閃AITING狀態(tài),然后返回線程的中斷標(biāo)記
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
線程釋放同步狀態(tài)后,需要喚醒繼任節(jié)點(diǎn),我們分析一下釋放同步狀態(tài)的邏輯
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
// 喚醒繼任節(jié)點(diǎn)
unparkSuccessor(h);
return true;
}
return false;
}
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;
// 節(jié)點(diǎn)狀態(tài)為負(fù)數(shù),則設(shè)置為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.
*/
Node s = node.next;
// 后繼節(jié)點(diǎn)為null或者其狀態(tài) > 0 (超時(shí)或者被中斷了)
if (s == null || s.waitStatus > 0) {
s = null;
// 從尾節(jié)點(diǎn)往前找未被中斷的節(jié)點(diǎn)
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);
}
總結(jié)
至此,我們分析完了整個(gè)AQS的幾個(gè)關(guān)鍵的方法,包括入隊(duì)列、獲取同步狀態(tài)(獨(dú)占式和共享式)、釋放同步狀態(tài)(獨(dú)占式和共享式)。