引言
可能我們平常很少聽說(shuō)它,就算是在并發(fā)里也很少用到它湘捎,但是我們所用的Condition(條件)、ReentrantLock(可重入鎖)舷胜、ReentrantReadWriteLock(讀寫鎖)就是根據(jù)它所構(gòu)建的活翩,它叫做隊(duì)列同步器,是構(gòu)建其他鎖和同步組件的基礎(chǔ)框架材泄。
同步器的主要使用方法是繼承,子類通過(guò)繼承同步器并實(shí)現(xiàn)它的抽象方法來(lái)管理同步狀態(tài)峦树,在抽象方法的實(shí)現(xiàn)過(guò)程中免不了對(duì)同步狀態(tài)進(jìn)行更改旦事,就需要使用同步器提供的3個(gè)最主要的方法
protected final int getState() ; //獲取同步狀態(tài)
protected final void setState(int newState) ;//設(shè)置同步狀態(tài)
protected final boolean compareAndSetState(int expect, int update) 族檬;//原子性設(shè)置同步狀態(tài)(使用CAS)
AQS本身是沒有實(shí)現(xiàn)任何同步接口的,它僅僅是定義了若干同步狀態(tài)的獲取和釋放來(lái)供自定義同步組件使用埋凯,它可以支持獨(dú)占式的獲取同步狀態(tài)(只能有一個(gè)線程使用,其他都得等待)白对,也支持共享式的獲取同步狀態(tài)(如讀寫鎖中的讀鎖)
同步器的設(shè)計(jì)是采用的模板方法設(shè)計(jì)模式,我們只需要重寫指定的方法蟀瞧,隨后將同步器組合在自定義同步組件中条摸,并調(diào)用模板方法提供的模板方法即可。
源碼分析
AQS里面可以重寫的主要方法如下(需要我們自己實(shí)現(xiàn)邏輯):
protected boolean tryAcquire(int arg) ; //獨(dú)占式的獲取鎖
protected boolean tryRelease(int arg) ;//獨(dú)占式的釋放鎖
protected int tryAcquireShared(int arg) ;//共享式的獲取鎖
protected boolean tryReleaseShared(int arg) ; //共享式的釋放鎖
protected boolean isHeldExclusively() ; //判斷當(dāng)前線程是否是在獨(dú)占模式下被線程占用钉蒲,一般表示是否被當(dāng)前線程所占用
AQS提供的主要模板方法如下(我們不能重寫):
public final void acquire(int arg) ; //獨(dú)占式獲取鎖
public final void acquireInterruptibly(int arg); //與acquire(int arg)一樣顷啼,但響應(yīng)中斷
public final boolean tryAcquireNanos(int arg, long nanosTimeout);//在acquireInterruptibly(int arg基礎(chǔ)上加入了超時(shí)機(jī)制,在規(guī)定時(shí)間內(nèi)沒有獲取到鎖返回false
獲取到了返回true
public final boolean release(int arg) ;//獨(dú)占式的釋放同步狀態(tài)
public final void acquireShared(int arg) ; //共享式的獲取鎖
public final void acquireSharedInterruptibly(int arg); //在acquireShared(int arg)加入了對(duì)中斷的響應(yīng)
public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout) //在acquireSharedInterruptibly(int arg)基礎(chǔ)上加入超時(shí)機(jī)制
public final boolean releaseShared(int arg) ; //共享式的釋放鎖
這上面的所有方法都加上了final,表明了它們不可繼承钙蒙,這就是模板方法設(shè)計(jì)模式
下面我們來(lái)看一下它的源碼
- 首先是獨(dú)占式同步鎖的獲取
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
這是獨(dú)占式獲取鎖的入口
具體流程如下:
1躬厌、用tryacquire(arg)去獲取同步狀態(tài)(需要我們自己實(shí)現(xiàn))
2、獲取不到把線程封裝Node節(jié)點(diǎn)(addwaiter),在加入等待隊(duì)列中
- tryAcquire(int arg)源碼
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
源碼就是簡(jiǎn)單地拋出異常烤咧,所以需要我們自己去實(shí)現(xiàn)
- addWaiter 源碼
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;
//嘗試加入到隊(duì)尾抢呆,因?yàn)檫@可能有多個(gè)線程競(jìng)爭(zhēng),采用compareAndSetTail(CAS操作)
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
//如果表頭為空又或者節(jié)點(diǎn)沒加入到隊(duì)尾
enq(node);
return node;
}
先構(gòu)造成Node節(jié)點(diǎn)
- Node是其內(nèi)部類,主要構(gòu)造如下
static final class Node {
volatile int waitStatus;
volatile Node prev;
volatile Node next;
volatile Thread thread;
Node nextWaiter;
}
prev:前驅(qū)節(jié)點(diǎn)昌阿;
next:后繼節(jié)點(diǎn)恳邀;
thread:進(jìn)入隊(duì)列的當(dāng)前線程
-
nextWaiter:存儲(chǔ)condition隊(duì)列中的后繼節(jié)點(diǎn)。
- waitStatus:節(jié)點(diǎn)狀態(tài)谣沸,主要有這幾種狀態(tài):
1、 CANCELLED:當(dāng)前線程被取消内地;
2、SIGNAL:當(dāng)前節(jié)點(diǎn)的后繼節(jié)點(diǎn)需要運(yùn)行阱缓;
3、 CONDITION:當(dāng)前節(jié)點(diǎn)在等待condition
4敞嗡、PROPAGATE:當(dāng)前場(chǎng)景下后續(xù)的acquireShared可以執(zhí)行航背;
- waitStatus:節(jié)點(diǎn)狀態(tài)谣沸,主要有這幾種狀態(tài):
隊(duì)列的基本結(jié)構(gòu)如下:
- enq源碼
/**
* Inserts node into queue, initializing if necessary. See picture above.
* @param node the node to insert
* @return node's predecessor
*/
private Node enq(final Node node) {
//死循環(huán)(自旋)地插入到隊(duì)尾
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
//通過(guò)compareAndSetTail保證節(jié)點(diǎn)能被線程安全添加
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
設(shè)置尾節(jié)點(diǎn)的過(guò)程
加入到同步隊(duì)列中之后沃粗,就執(zhí)行 acquireQueued
- acquireQueued源碼
/**
* 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;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
節(jié)點(diǎn)進(jìn)入同步隊(duì)列后,就開始自旋突雪,每個(gè)節(jié)點(diǎn)都在觀察自己是否滿足條件涡贱,當(dāng)“條件”滿足,就可以獲取同步狀態(tài)问词,然后從自旋地過(guò)程中退出
這里有兩點(diǎn)要說(shuō)明:
1、頭節(jié)點(diǎn)是獲取到同步狀態(tài)的點(diǎn)激挪,而頭節(jié)點(diǎn)的線程釋放同步狀態(tài)后,會(huì)喚醒其后續(xù)節(jié)點(diǎn)宛篇,所以每個(gè)節(jié)點(diǎn)都在判斷自己的前驅(qū)節(jié)點(diǎn)是否是頭節(jié)點(diǎn)薄湿,是之后看能不能獲取到同步狀態(tài),只有兩者都滿足時(shí)豺瘤,才能退出
2、維護(hù)同步隊(duì)列的是fifo原則(先進(jìn)先出)蚕泽,整個(gè)過(guò)程如圖所示:
總結(jié)一下獨(dú)占式獲取鎖的流程:
當(dāng)前線程獲取到同步狀態(tài)后桥嗤,就需要釋放同步狀態(tài)派任,使后續(xù)節(jié)點(diǎn)能夠獲取璧南,主要是通過(guò)release(int arg) 實(shí)現(xiàn)的
- 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;
}
tryrelease:判斷能否釋放同步狀態(tài)
unparkSuccessor(h):?jiǎn)拘押罄m(xù)節(jié)點(diǎn)
上面講了獨(dú)占式獲取獲取鎖,下面講講共享式獲取
共享式和獨(dú)占式最大的區(qū)別是同一時(shí)刻能否有多個(gè)線程同時(shí)獲取到同步狀態(tài)豆混,如圖所示
共享式獲取的入口是acquireShared 方法
- acquireShared 源碼
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
tryAcquireShared方法是需要我們自己實(shí)現(xiàn)的动知,返回的是int值,當(dāng)返回值大于0時(shí)盒粮,表示能獲取到同步狀態(tài)
- doAcquireShared源碼
private void doAcquireShared(int arg) {
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
先把線程加入到同步隊(duì)列中丹皱,然后死循環(huán),判斷前驅(qū)節(jié)點(diǎn)是否是頭節(jié)點(diǎn)摊崭,然后獲取同步狀態(tài),當(dāng)兩者都滿足是就退出循環(huán)
與獨(dú)占式獲取同步狀態(tài)一樣矮台,共享式獲取也是需要釋放同步狀態(tài)的,AQS提供releaseShared(int arg)方法可以釋放同步狀態(tài)瘦赫。
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
1蛤迎、調(diào)用tryReleaseShared方法釋放狀態(tài);
2忘苛、 調(diào)用doReleaseShared方法喚醒后繼節(jié)點(diǎn)唱较;