Java并發(fā)編程的藝術(shù)筆記
- 1.并發(fā)編程的挑戰(zhàn)
- 2.Java并發(fā)機制的底層實現(xiàn)原理
- 3.Java內(nèi)存模型
- 4.Java并發(fā)編程基礎(chǔ)
- 5.Java中的鎖的使用和實現(xiàn)介紹
- 6.Java并發(fā)容器和框架
- 7.Java中的12個原子操作類介紹
- 8.Java中的并發(fā)工具類
- 9.Java中的線程池
- 10.Executor框架
目錄
Lock接口
隊列同步器
重入鎖
讀寫鎖
LockSupport工具
Condition接口
小結(jié)
Lock接口
在Java SE 5
之后强霎,并發(fā)包中新增了Lock
接口(以及相關(guān)實現(xiàn)類)用來實現(xiàn)鎖功能堕义,它提供了與synchronized
關(guān)鍵字類似的同步功能,只是在使用時需要 顯式 地獲取和釋放鎖脆栋。
雖然它缺少了(通過synchronized
塊或者方法所提供的)隱式獲取釋放鎖的便捷性倦卖,但是卻擁有了鎖獲取與釋放的 可操作性、可中斷的獲取鎖 以及 超時獲取鎖 等多種synchronized
關(guān)鍵字所不具備的同步特性椿争。
使用synchronized
關(guān)鍵字將會 隱式 地獲取鎖怕膛,但是它將鎖的獲取和釋放固化了,也就是先獲取再釋放秦踪。
Lock
的使用:
Lock lock = new ReentrantLock();
lock.lock();
try {
} finally {
lock.unlock();
}
注意 :
1.在
finally
塊中釋放鎖褐捻,目的是保證在獲取到鎖之后,最終能夠被釋放椅邓。
2.不要將獲取鎖的過程寫在try
塊中柠逞,因為如果在獲取鎖(自定義鎖的實現(xiàn))時發(fā)生了異常,異常拋出的同時景馁,也會導(dǎo)致鎖無故釋放板壮。
Lock
接口提供的synchronized
關(guān)鍵字所不具備的主要特性:
- 嘗試非阻塞性獲取鎖: 當(dāng)前線程嘗試獲取鎖,如果此時沒有其他線程占用此鎖合住,則成功獲取到鎖绰精。
- 能被中斷的獲取鎖: 當(dāng)獲取到鎖的線程被中斷時,中斷異常會拋出并且會釋放鎖透葛。
- 超時獲取鎖: 在指定時間內(nèi)獲取鎖笨使,如果超過時間還沒獲取,則返回僚害。
Lock
相關(guān)的API:
-
void lock();
:獲取鎖硫椰,獲取之后返回 -
void lockInterruptibly() throws InterruptedException;
:可中斷的獲取鎖 -
boolean tryLock();
:嘗試非阻塞的獲取鎖 -
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
: 超時獲取鎖。 超時時間結(jié)束萨蚕,未獲得鎖靶草,返回false
. -
void unlock();
:釋放鎖 -
Condition newCondition();
:獲取等待通知組件,改組件和鎖綁定门岔,當(dāng)前線程獲取到鎖才能調(diào)用wait()
方法爱致,調(diào)用之后則會釋放鎖。
隊列同步器
隊列同步器AbstractQueuedSynchronizer
(以下簡稱同步器)寒随,是用來構(gòu)建鎖或者其他同步組件的基礎(chǔ)框架,它使用了一個int
成員變量表示同步狀態(tài),通過內(nèi)置的FIFO
隊列來完成資源獲取線程的排隊工作妻往,并發(fā)包的作者Doug Lea
期望它能夠成為實現(xiàn)大部分同步需求的基礎(chǔ)互艾。
同步器的主要使用方式是繼承AbstractQueuedSynchronizer
,通過同步器提供的3個方法getState()
讯泣、setState(int newState)
和compareAndSetState(int expect,int update)
來進行線程安全的狀態(tài)同步纫普。
同步器是實現(xiàn)鎖的關(guān)鍵,在鎖的實現(xiàn)中聚合同步器好渠,利用同步器實現(xiàn)鎖的語義昨稼。
可以這樣理解二者之間的關(guān)系:
- 鎖是面向使用者的,它定義了使用者與鎖交互的接口拳锚,隱藏了實現(xiàn)細(xì)節(jié)假栓;
- 同步器面向的是鎖的實現(xiàn)者,它簡化了鎖的實現(xiàn)方式霍掺,屏蔽了同步狀態(tài)管理匾荆、線程的排隊、等待與喚醒等底層操作杆烁。
隊列同步器的接口與示例
AbstractQueuedSynchronizer
可重寫的方法:
-
boolean tryAcquire(int arg)
:獨占式獲取同步狀態(tài)牙丽。 -
boolean tryRelease(int arg)
:獨占式釋放同步狀態(tài)。 -
int tryAcquireShared(int arg)
:共享式獲取同步狀態(tài)兔魂。 -
boolean tryReleaseShared(int arg)
:共享釋放取同步狀態(tài)烤芦。 -
boolean isHeldExclusively()
:當(dāng)前同步器是否在獨占式模式下被線程占用。
實現(xiàn)自定義同步組件時析校,將會調(diào)用同步器提供 獨占式獲取與釋放同步狀態(tài)拍棕、共享式獲取與釋放同步狀態(tài) 和 查詢同步隊列中的等待線程情況 三類模板方法。
獨占鎖的示例代碼:
/**
* @author https://github.com/103style
* @date 2019/6/12 17:32
*/
public class TestLock implements Lock {
private TestQueuedSync sync;
/**
* 獲取鎖
*/
@Override
public void lock() {
sync.acquire(1);
}
/**
* 可中斷的獲取鎖
*/
@Override
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
/**
* 嘗試非阻塞式獲取鎖
*/
@Override
public boolean tryLock() {
return sync.tryAcquire(1);
}
/**
* 嘗試非阻塞式獲取鎖
*/
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return sync.tryAcquire(1);
}
/**
* 釋放鎖
*/
@Override
public void unlock() {
sync.release(1);
}
@Override
public Condition newCondition() {
return sync.newCondition();
}
/**
* 是否有同步隊列線程
*/
public boolean hasQueuedThreads() {
return sync.hasQueuedThreads();
}
/**
* 鎖是否被占用
*/
public boolean isLock() {
return sync.isHeldExclusively();
}
private static class TestQueuedSync extends AbstractQueuedSynchronizer {
/**
* 獨占式獲取同步狀態(tài)
*/
@Override
protected boolean tryAcquire(int arg) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
/**
* 獨占式釋放同步狀態(tài)
*/
@Override
protected boolean tryRelease(int arg) {
if (getState() == 0) {
throw new IllegalStateException();
}
setExclusiveOwnerThread(null);
setState(0);
return true;
}
/**
* 同步狀態(tài)是否被占用
*/
@Override
protected boolean isHeldExclusively() {
return getState() == 1;
}
/**
* 返回一個Condition勺良,每個condition都包含了一個condition隊列
*/
Condition newCondition() {
return new ConditionObject();
}
}
}
上述示例代碼中绰播,獨占鎖TestLock
是一個自定義同步組件,它在同一時刻只允許一個線程占有鎖尚困。TestLock
中定義了一個靜態(tài)內(nèi)部類TestQueuedSync
繼承了同步器蠢箩,在tryAcquire(int acquires)
方法中,如果經(jīng)過compareAndSetState
設(shè)置成功事甜,則代表獲取了同步狀態(tài)1
谬泌,而在tryRelease(int releases)
方法中只是將同步狀態(tài)重置為0
。
用戶使用TestLock
時并不會直接和內(nèi)部同步器的實現(xiàn)TestQueuedSync
打交道逻谦,而是調(diào)用TestLock
提供的方法掌实,在TestLock
的實現(xiàn)中,以獲取鎖的lock()
方法為例邦马,只需要在方法實現(xiàn)中調(diào)用同步器的模板方法acquire(int args)
即可贱鼻,當(dāng)前線程調(diào)用該方法獲取同步狀態(tài)失敗后會被加入到同步隊列中等待宴卖,這樣就大大降低了實現(xiàn)一個可靠自定義同步組件的門檻。
隊列同步器的實現(xiàn)分析
接下來將從實現(xiàn)角度分析同步器是如何完成線程同步的:
-
同步隊列 : 一個
FIFO
雙向隊列邻悬。
當(dāng)前線程獲取同步狀態(tài)失敗時症昏,同步器會將當(dāng)前線程以及等待狀態(tài)等信息構(gòu)造成為一個節(jié)點Node
并將其加入同步隊列,同時會阻塞當(dāng)前線程父丰,當(dāng)同步狀態(tài)釋放時肝谭,會把首節(jié)點中的線程喚醒,使其再次嘗試獲取同步狀態(tài)蛾扇。
Node
保存 獲取同步狀態(tài)失敗的線程引用攘烛、等待狀態(tài) 以及 前驅(qū)和后繼節(jié)點,節(jié)點的屬性類型 與 名稱 以及 描述 如下:/** * 等待狀態(tài): * CANCELLED : 1 在同步隊列中等待超時或被中斷镀首,需要從隊列中取消等待坟漱,在該狀態(tài)將不會變化 * SIGNAL : -1 后繼節(jié)點地線程處于等待狀態(tài),當(dāng)前節(jié)點釋放獲取取消同步狀態(tài)蘑斧,后繼節(jié)點地線程即開始運行 * CONDITION : -2 在等待隊列中靖秩, * PROPAGATE : -3 下一次共享式同步狀態(tài)獲取將會無條件地被傳播下去 * INITAL : 0 初始狀態(tài) */ volatile int waitStatus; volatile Node prev;//前驅(qū)節(jié)點 volatile Node next;//后繼節(jié)點 volatile Thread thread;//獲取同步狀態(tài)的線程 Node nextWaiter;//等待隊列中的后繼節(jié)點。 如果節(jié)點是共享的的竖瘾,這個字段將是一個SHARED常量
如上圖所示沟突,同步器包含了兩個節(jié)點類型的引用,一個指向 頭節(jié)點捕传,而另一個指向 尾節(jié)點惠拭。
試想一下,當(dāng)一個線程成功地獲取了同步狀態(tài)(或者鎖)庸论,其他線程將無法獲取到同步狀態(tài)职辅,轉(zhuǎn)而被構(gòu)造成為節(jié)點并加入到同步隊列中,而這個加入隊列的過程必須要保證線程安全聂示,因此同步器提供了一個基于CAS的設(shè)置尾節(jié)點的方法:compareAndSetTail(Node expect,Node update)
域携,它需要傳遞當(dāng)前線程“認(rèn)為”的尾節(jié)點和當(dāng)前節(jié)點,只有設(shè)置成功后鱼喉,當(dāng)前節(jié)點才正式與之前的尾節(jié)點建立關(guān)聯(lián)秀鞭。
-
獨占式同步狀態(tài)獲取與釋放
通過調(diào)用同步器的acquire(int arg)
方法可以獲取同步狀態(tài),該方法對中斷不敏感扛禽,也就是由于線程獲取同步狀態(tài)失敗后進入同步隊列中锋边,后續(xù)對線程進行中斷操作時,線程不會從同步隊列中移出编曼。acquire(int arg)
代碼如下:public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }
上述代碼主要完成了 同步狀態(tài)獲取豆巨、節(jié)點構(gòu)造、加入同步隊列 以及 在同步隊列中自旋等待掐场。
- 首先調(diào)用自定義同步器實現(xiàn)的
tryAcquire(int arg)
方法保證線程安全的獲取同步狀態(tài) - 如果獲取同步狀態(tài)失敗往扔,構(gòu)造同步節(jié)點(獨占式
Node.EXCLUSIVE
贩猎,同一時刻只能有一個線程成功獲取同步狀態(tài))并通過addWaiter(Node node)
方法將該節(jié)點加入到同步隊列的尾部。 - 最后調(diào)用
acquireQueued(Node node,int arg)
方法瓤球,使得該節(jié)點以“死循環(huán)”的方式獲取同步狀態(tài) - 如果獲取不到則阻塞節(jié)點中的線程融欧,而 被阻塞線程的喚醒 主要依靠 前驅(qū)節(jié)點的出隊或 阻塞線程被中斷 來實現(xiàn)葛假。
我們來看下節(jié)點的構(gòu)造以及加入同步隊列的
addWaiter(Node mode)
和initializeSyncQueue()
方法:private Node addWaiter(Node mode) { Node node = new Node(mode); for (;;) { Node oldTail = tail; if (oldTail != null) { U.putObject(node, Node.PREV, oldTail); if (compareAndSetTail(oldTail, node)) { oldTail.next = node; return node; } } else { initializeSyncQueue(); } } } private final void initializeSyncQueue() { Node h; if (U.compareAndSwapObject(this, HEAD, null, (h = new Node()))) tail = h; }
上述代碼通過在“死循環(huán)”中使用
compareAndSetTail(Node expect,Node update)
方法來確保節(jié)點能夠被線程安全添加姆吭。 如果沒有尾節(jié)點的話宫患,則構(gòu)建一個新的同步隊列。接下來看下
acquireQueued(final Node node, int arg)
方法:final boolean acquireQueued(final Node node, int arg) { try { boolean interrupted = false; for (;;) { final Node p = node.predecessor(); if (p == head && tryAcquire(arg)) { setHead(node); p.next = null; // help GC return interrupted; } if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } catch (Throwable t) { cancelAcquire(node); throw t; } }
在
acquireQueued(final Node node,int arg)
方法中绿饵,當(dāng)前線程在“死循環(huán)”中嘗試獲取同步狀態(tài),而只有前驅(qū)節(jié)點是頭節(jié)點才能夠嘗試獲取同步狀態(tài)瓶颠,這是為什么拟赊?原因有兩個:- 頭節(jié)點是成功獲取到同步狀態(tài)的節(jié)點,而頭節(jié)點的線程釋放了同步狀態(tài)之后粹淋,將會喚醒其后繼節(jié)點吸祟,后繼節(jié)點的線程被喚醒后需要檢查自己的前驅(qū)節(jié)點是否是頭節(jié)點。
-
維護同步隊列的FIFO原則桃移。
節(jié)點自旋獲取同步狀態(tài)
由于非首節(jié)點線程前驅(qū)節(jié)點出隊或者被中斷而從等待狀態(tài)返回屋匕,隨后檢查自己的前驅(qū)是否是頭節(jié)點,如果是則嘗試獲取同步狀態(tài)借杰」牵可以看到節(jié)點和節(jié)點之間在循環(huán)檢查的過程中基本不相互通信,而是簡單地判斷自己的前驅(qū)是否為頭節(jié)點蔗衡,這樣就使得節(jié)點的釋放規(guī)則符合FIFO纤虽,并且也便于對過早通知的處理(過早通知是指前驅(qū)節(jié)點不是頭節(jié)點的線程由于中斷而被喚醒)。
獨占式同步狀態(tài)獲取流程調(diào)用同步器的
release(int arg)
方法可以釋放同步狀態(tài)绞惦,然后會喚醒其后繼節(jié)點(進而使后繼節(jié)點重新嘗試獲取同步狀態(tài))逼纸。release(int arg)
執(zhí)行之后會喚醒后繼的節(jié)點。public final boolean release(int arg) { if (tryRelease(arg)) { Node h = head; if (h != null && h.waitStatus != 0) unparkSuccessor(h); return true; } return false; } private void unparkSuccessor(Node node) { int ws = node.waitStatus; if (ws < 0) node.compareAndSetWaitStatus(ws, 0); Node s = node.next; if (s == null || s.waitStatus > 0) { s = null; for (Node p = tail; p != node && p != null; p = p.prev) if (p.waitStatus <= 0) s = p; } if (s != null) LockSupport.unpark(s.thread); }
- 首先調(diào)用自定義同步器實現(xiàn)的
-
共享式同步狀態(tài)獲取與釋放
共享式獲取 與 獨占式獲取 最主要的區(qū)別在于 同一時刻能否有多個線程同時獲取到同步狀態(tài)济蝉。以文件的讀寫為例杰刽,寫操作 要求對資源的 獨占式訪問 ,而 讀操作 可以是 共享式訪問堆生。
通過調(diào)用
acquireShared(int arg)
可以共享式地獲取同步狀態(tài)专缠,方法代碼如下:public final void acquireShared(int arg) { if (tryAcquireShared(arg) < 0) doAcquireShared(arg); } private void doAcquireShared(int arg) { final Node node = addWaiter(Node.SHARED); 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(); return; } } if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } catch (Throwable t) { cancelAcquire(node); throw t; } }
在共享式獲取的自旋過程中,成功獲取到同步狀態(tài)并退出自旋的條件就是
tryAcquireShared(int arg)
方法返回值大于等于0
淑仆。
在doAcquireShared(int arg)
方法的自旋過程中涝婉,如果當(dāng)前節(jié)點的 前驅(qū)為頭節(jié)點 時,嘗試獲取同步狀態(tài)蔗怠,如果返回值大于等于0
墩弯,表示該次獲取同步狀態(tài)成功并從自旋過程中退出吩跋。共享式獲取也需要釋放同步狀態(tài),通過調(diào)用
releaseShared(int arg)
方法可以釋放同步狀態(tài):public final boolean releaseShared(int arg) { if (tryReleaseShared(arg)) { doReleaseShared(); return true; } return false; } private void doReleaseShared() { for (;;) { Node h = head; if (h != null && h != tail) { int ws = h.waitStatus; if (ws == Node.SIGNAL) { if (!h.compareAndSetWaitStatus(Node.SIGNAL, 0)) continue; // loop to recheck cases unparkSuccessor(h); } else if (ws == 0 && !h.compareAndSetWaitStatus(0, Node.PROPAGATE)) continue; // loop on failed CAS } if (h == head) // loop if head changed break; } }
該方法在釋放同步狀態(tài)之后渔工,將會喚醒后續(xù)處于等待狀態(tài)的節(jié)點锌钮。對于能夠支持多個線程同時訪問的并發(fā)組件(比如
Semaphore
),它和獨占式主要區(qū)別在于tryReleaseShared(int arg)
方法必須確保同步狀態(tài)(或者資源數(shù))線程安全釋放引矩,一般是通過循環(huán)和CAS來保證的梁丘,因為釋放同步狀態(tài)的操作會同時來自多個線程。 -
超時獲取同步狀態(tài)
通過調(diào)用同步器的doAcquireNanos(int arg,long nanosTimeout)
方法可以超時獲取同步狀態(tài)旺韭,即在指定的時間段內(nèi)獲取同步狀態(tài)氛谜,如果獲取到同步狀態(tài)則返回true
,否則区端,返回false
值漫。該方法提供了傳統(tǒng)Java同步操作(比如synchronized
關(guān)鍵字)所不具備的特性。private boolean doAcquireNanos(int arg, long nanosTimeout) throws InterruptedException { if (nanosTimeout <= 0L) return false; final long deadline = System.nanoTime() + nanosTimeout; final Node node = addWaiter(Node.EXCLUSIVE); try { for (;;) { final Node p = node.predecessor(); if (p == head && tryAcquire(arg)) { setHead(node); p.next = null; // help GC return true; } nanosTimeout = deadline - System.nanoTime(); if (nanosTimeout <= 0L) { cancelAcquire(node); return false; } if (shouldParkAfterFailedAcquire(p, node) && nanosTimeout > SPIN_FOR_TIMEOUT_THRESHOLD) LockSupport.parkNanos(this, nanosTimeout); if (Thread.interrupted()) throw new InterruptedException(); } } catch (Throwable t) { cancelAcquire(node); throw t; } }
該方法在自旋過程中织盼,當(dāng)節(jié)點的前驅(qū)節(jié)點為頭節(jié)點時嘗試獲取同步狀態(tài)杨何,如果獲取成功則從該方法返回,這個過程和獨占式同步獲取的過程類似沥邻。獲取失敗則會重新計算超時時間危虱。
如果
nanosTimeout
小于等于spinForTimeoutThreshold
(1000納秒
)時,將不會使該線程進行超時等待谋国,而是進入快速的自旋過程槽地。原因在于,非常短的超時等待無法做到十分精確芦瘾。獨占式超時獲取同步態(tài) 的流程下:
獨占式超時獲取同步狀態(tài)的流程 -
自定義同步組件
設(shè)計一個同步工具:在同一時刻捌蚊,只允許至多兩個線程同時訪問,超過兩個線程的訪問將被阻塞近弟。
能夠在同一時刻支持多個線程的訪問(共享式訪問)缅糟。
代碼如下:
省略了無關(guān)的重寫方法
/** * @author xiaoke.luo@tcl.com 2019/6/14 11:15 * 自定義同步組件 * <p> * 實現(xiàn)以下功能 * 1.在同一時刻,只允許至多兩個線程同時訪問祷愉,超過兩個線程的訪問將被阻塞窗宦。 * 2.能夠在同一時刻支持多個線程的訪問(共享式訪問)。 */ public class CustomLock implements Lock { private CustomSyncQueue customSyncQueue = new CustomSyncQueue(2); public static void main(String[] args) { final Lock lock = new CustomLock(); class Worker extends Thread { @Override public void run() { while (true) { lock.lock(); try { SleepUtils.second(1); System.out.println(Thread.currentThread().getName()); SleepUtils.second(1); } finally { lock.unlock(); } } } } // 啟動10個線程 for (int i = 0; i < 10; i++) { Worker w = new Worker(); w.setDaemon(true); w.start(); } // 每隔1秒換行 for (int i = 0; i < 10; i++) { SleepUtils.second(1); System.out.println(); } } @Override public void lock() { customSyncQueue.tryAcquireShared(1); } @Override public void unlock() { customSyncQueue.tryReleaseShared(1); } public static class CustomSyncQueue extends AbstractQueuedSynchronizer { public CustomSyncQueue(int count) { if (count <= 0) { throw new IllegalStateException("count must >= 0"); } setState(count); } @Override protected int tryAcquireShared(int reduceCount) { for (; ; ) { int current = getState(); int newCount = current - reduceCount; if (newCount < 0 || compareAndSetState(current, newCount)) { return newCount; } } } @Override protected boolean tryReleaseShared(int returnCount) { for (; ; ) { int current = getState(); int newCount = current + returnCount; if (compareAndSetState(current, newCount)) { return true; } } } } }
上述代碼主要還是
CustomSyncQueue
的tryAcquireShared
和tryReleaseShared
方法二鳄,當(dāng)tryAcquireShared(int reduceCount)
方法返回值>=0
時赴涵,當(dāng)前線程才獲取同步狀態(tài)。
重入鎖
重入鎖ReentrantLock
订讼,就是支持重進入的鎖髓窜,它表示該鎖能夠支持 一個線程對資源的重復(fù)加鎖。
除此之外,該鎖的還支持獲取鎖時的公平和非公平性選擇寄纵。
我們回顧下TestLock
的lock
方法鳖敷,在 tryAcquire(int acquires)
方法時沒有考慮占有鎖的線程再次獲取鎖的場景,而在調(diào)用tryAcquire(int acquires)
方法時返回了false
程拭,導(dǎo)致該線程被阻塞定踱。
在絕對時間上,先對鎖進行獲取的請求一定先被滿足恃鞋,那么這個鎖是公平的崖媚,反之,是不公平的山宾。
事實上至扰,公平的鎖機制往往沒有非公平的效率高鳍徽,但是资锰,并不是任何場景都是以TPS作為唯一的指標(biāo),公平鎖能夠減少“饑餓”發(fā)生的概率阶祭,等待越久的請求越是能夠得到優(yōu)先滿足绷杜。
下面我們來分析下ReentrantLock
的實現(xiàn):
-
實現(xiàn)重進入
重進入是指任意線程在獲取到鎖之后能夠再次獲取該鎖而不會被鎖所阻塞,該特性的實現(xiàn)需要解決以下兩個問題:- 線程再次獲取鎖
- 鎖的最終釋放
下面是
ReentrantLock
通過組合自定義同步器來實現(xiàn)鎖的獲取與釋放濒募,以非公平性(默認(rèn)的)實現(xiàn):final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { 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; }
此方法通過判斷 當(dāng)前線程是否為獲取鎖的線程 來決定獲取操作是否成功鞭盟,如果是獲取鎖的線程再次請求,則將同步狀態(tài)值進行增加并返回
true
瑰剃,表示獲取同步狀態(tài)成功齿诉。下面看釋放鎖的方法
tryRelease(int releases)
: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; }
通過檢查
state == 0
來判斷是否需要繼續(xù)釋放鎖。 -
公平與非公平獲取鎖的區(qū)別
公平性與否是針對獲取鎖而言的晌姚,如果一個鎖是公平的粤剧,那么鎖的獲取順序就應(yīng)該符合請求的絕對時間順序,也就是 FIFO挥唠。
對于上面介紹的非公平鎖實現(xiàn)的nonfairTryAcquire(int acquires)
抵恋,只要 CAS 設(shè)置同步狀態(tài)成功,即獲取到鎖宝磨,而公平鎖則不同弧关,如下:protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { 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; }
相比非公平鎖的實現(xiàn),公平鎖的實現(xiàn)在獲取鎖的時候多了一個
!hasQueuedPredecessors()
判斷:public final boolean hasQueuedPredecessors() { Node t = tail; // Read fields in reverse initialization order Node h = head; Node s; return h != t && ((s = h.next) == null || s.thread != Thread.currentThread()); }
即加入了 同步隊列中當(dāng)前節(jié)點是否有前驅(qū)節(jié)點的判斷 唤锉,如果該方法返回
true
世囊,則表示有線程比當(dāng)前線程更早地請求獲取鎖,因此 需要等待前驅(qū)線程獲取并釋放鎖之后才能繼續(xù)獲取鎖窿祥。
讀寫鎖
之前提到鎖(如TestLock
和ReentrantLock
)基本都是排他鎖株憾,這些鎖在同一時刻只允許一個線程進行訪問,而讀寫鎖在同一時刻可以允許多個讀線程訪問壁肋,但是在寫線程訪問時号胚,所有的讀線程和其他寫線程均被阻塞籽慢。
讀寫鎖維護了一對鎖,一個 讀鎖 和一個 寫鎖猫胁,通過分離讀鎖和寫鎖箱亿,使得并發(fā)性相比一般的排他鎖有了很大提升。
一般情況下弃秆,讀寫鎖 的性能都會比 排它鎖 好届惋,因為大多數(shù)場景 讀是多于寫 的。在讀多于寫的情況下菠赚,讀寫鎖 能夠提供比 排它鎖 更好的 并發(fā)性 和 吞吐量脑豹。Java并發(fā)包提供讀寫鎖的實現(xiàn)是ReentrantReadWriteLock
,特性如下:
- 公平性選擇 :支持公平和非公平的方式獲取鎖衡查,吞吐量非公平優(yōu)于公平瘩欺。
- 重進入 : 讀鎖在獲取鎖之后再獲取讀鎖,寫鎖在獲取鎖之后再獲取讀鎖和寫鎖拌牲。
- 鎖降級 :遵循獲取寫鎖俱饿、獲取讀鎖在釋放寫鎖的次序,寫鎖能夠降級為讀鎖塌忽。
讀寫鎖的接口與示例
ReadWriteLock
僅定義了獲取讀鎖和寫鎖的兩個方法拍埠,即readLock()
方法和writeLock()
方法,而其實現(xiàn)類ReentrantReadWriteLock
土居,除了接口方法之外枣购,還提供了一些便于外界監(jiān)控其內(nèi)部工作狀態(tài)的方法,這些方法如下:
-
getReadLockCount()
:返回當(dāng)前讀鎖獲取的次數(shù) -
getReadHoldCount()
:返回當(dāng)前線程獲取讀鎖的次數(shù) -
isWriteLocked()
:判斷寫鎖是否被獲取 -
getWriteHoldCount()
:返回當(dāng)前寫鎖被獲取的次數(shù)
以下是讀寫鎖的使用示例代碼:
通過讀寫鎖保證 非線程安全的HashMap
的讀寫是線程安全的擦耀。
static Map<String, Object> map = new HashMap<>();
static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
static Lock r = rwl.readLock();
static Lock w = rwl.writeLock();
/**
* 獲取一個key對應(yīng)的value
*/
public static final Object get(String key) {
r.lock();
try {
return map.get(key);
} finally {
r.unlock();
}
}
/**
* 設(shè)置key對應(yīng)的value棉圈,并返回舊的value
*/
public static final Object put(String key, Object value) {
w.lock();
try {
return map.put(key, value);
} finally {
w.unlock();
}
}
/**
* 清空所有的內(nèi)容
*/
public static final void clear() {
w.lock();
try {
map.clear();
} finally {
w.unlock();
}
}
讀寫鎖的實現(xiàn)分析
主要包括:讀寫狀態(tài)的設(shè)計、寫鎖的獲取與釋放埂奈、讀鎖的獲取與釋放以及鎖降級迄损。
-
讀寫狀態(tài)的設(shè)計
讀寫鎖將變量切分成了兩個部分,高16位表示讀账磺,低16位表示寫芹敌,如下圖:
讀寫鎖狀態(tài)的劃分方式當(dāng)前同步狀態(tài)表示一個線程已經(jīng)獲取了寫鎖,且重進入了兩次垮抗,同時也連續(xù)獲取了兩次讀鎖氏捞。
讀寫鎖是如何迅速確定讀和寫各自的狀態(tài)呢?答案是通過位運算冒版。
假設(shè)當(dāng)前同步狀態(tài)值為S
液茎,寫狀態(tài)等于S&0x0000FFFF
(將高16位全部抹去),讀狀態(tài)等于S>>16
(無符號補0右移16位)。
當(dāng)寫狀態(tài)增加1
時捆等,等于S+1
滞造,當(dāng)讀狀態(tài)增加1
時,等于S+(1<<16)
栋烤,也就是S+0x00010000
谒养。
根據(jù)狀態(tài)的劃分能得出一個推論:S != 0
時,當(dāng)寫狀態(tài)S&0x0000FFFF = 0
時明郭,則讀狀態(tài)S>>16 > 0
买窟,即讀鎖已被獲取。 -
寫鎖的獲取與釋放
寫鎖是一個支持重進入的排它鎖薯定。如果當(dāng)前線程已經(jīng)獲取了寫鎖始绍,則增加寫狀態(tài)。如果當(dāng)前線程在獲取寫鎖時话侄,讀鎖已經(jīng)被獲瓤魍啤(讀狀態(tài)不為0)或者該線程不是已經(jīng)獲取寫鎖的線程,則當(dāng)前線程進入等待狀態(tài)满葛。
ReentrantReadWriteLoc
的tryAcquire
方法如下:protected final boolean tryAcquire(int acquires) { Thread current = Thread.currentThread(); int c = getState(); int w = exclusiveCount(c); if (c != 0) { // (Note: if c != 0 and w == 0 then shared count != 0) // 存在讀鎖或者當(dāng)前獲取線程不是已經(jīng)獲取寫鎖的線程 if (w == 0 || current != getExclusiveOwnerThread()) return false; if (w + exclusiveCount(acquires) > MAX_COUNT) throw new Error("Maximum lock count exceeded"); // Reentrant acquire setState(c + acquires); return true; } if (writerShouldBlock() || !compareAndSetState(c, c + acquires)) return false; setExclusiveOwnerThread(current); return true; }
該方法除了重入條件(當(dāng)前線程為獲取了寫鎖的線程)之外径簿,增加了一個 讀鎖是否存在 的判斷。如果存在讀鎖嘀韧,則寫鎖不能被獲取。
寫鎖的釋放與
ReentrantLock
的釋放過程基本類似缠捌,每次釋放均減少寫狀態(tài)锄贷,當(dāng)寫狀態(tài)為0
時表示寫鎖已被釋放,從而等待的讀寫線程能夠繼續(xù)訪問讀寫鎖曼月,同時前次寫線程的修改對后續(xù)讀寫線程可見谊却。 -
讀鎖的獲取與釋放
讀鎖是一個支持重進入的 共享鎖,它能夠被多個線程同時獲取哑芹,在沒有其他寫線程訪問(或者寫狀態(tài)為0
)時炎辨,讀鎖總會被成功地獲取,而所做的也只是(線程安全的)增加讀狀態(tài)聪姿。如果當(dāng)前線程已經(jīng)獲取了讀鎖碴萧,則增加讀狀態(tài)。如果當(dāng)前線程在獲取讀鎖時末购,寫鎖已被其他線程獲取破喻,則進入等待狀態(tài)。
ReentrantReadWriteLock
的tryAcquireShared
方法:protected final int tryAcquireShared(int unused) { Thread current = Thread.currentThread(); int c = getState(); if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current) return -1; int r = sharedCount(c); if (!readerShouldBlock() && r < MAX_COUNT && compareAndSetState(c, c + SHARED_UNIT)) { ... return 1; } return fullTryAcquireShared(current); } final int fullTryAcquireShared(Thread current) { HoldCounter rh = null; for (;;) { int c = getState(); if (exclusiveCount(c) != 0) { if (getExclusiveOwnerThread() != current) return -1; // else we hold the exclusive lock; blocking here // would cause deadlock. } else if (readerShouldBlock()) { // Make sure we're not acquiring read lock reentrantly if (firstReader == current) { // assert firstReaderHoldCount > 0; } else { if (rh == null) { rh = cachedHoldCounter; if (rh == null || rh.tid != getThreadId(current)) { rh = readHolds.get(); if (rh.count == 0) readHolds.remove(); } } if (rh.count == 0) return -1; } } if (sharedCount(c) == MAX_COUNT) throw new Error("Maximum lock count exceeded"); if (compareAndSetState(c, c + SHARED_UNIT)) { if (sharedCount(c) == 0) { firstReader = current; firstReaderHoldCount = 1; } else if (firstReader == current) { firstReaderHoldCount++; } else { if (rh == null) rh = cachedHoldCounter; if (rh == null || rh.tid != getThreadId(current)) rh = readHolds.get(); else if (rh.count == 0) readHolds.set(rh); rh.count++; cachedHoldCounter = rh; // cache for release } return 1; } } }
如果其他線程已經(jīng)獲取了寫鎖盟榴,則當(dāng)前線程獲取讀鎖失敗曹质,進入等待狀態(tài)。
如果當(dāng)前線程獲取了寫鎖或者寫鎖未被獲取,則當(dāng)前線程增加讀狀態(tài)羽德,成功獲取讀鎖几莽。讀鎖的每次釋放均減少讀狀態(tài),減少的值是
1<<16
宅静。 -
鎖降級
鎖降級指的是 寫鎖降級成為讀鎖银觅。
如果當(dāng)前線程擁有寫鎖,然后將其釋放坏为,最后再獲取讀鎖究驴,這種分段完成的過程不能稱之為鎖降級。鎖降級是指把持自确(當(dāng)前擁有的)寫鎖洒忧,再獲取到讀鎖,隨后釋放(先前擁有的)寫鎖的過程够颠。
以下是是鎖降級的示例://當(dāng)數(shù)據(jù)發(fā)生變更后熙侍,update變量(布爾類型且volatile修飾)被設(shè)置為false public void processData() { readLock.lock(); if (!update) { // 必須先釋放讀鎖 readLock.unlock(); // 鎖降級從寫鎖獲取到開始 writeLock.lock(); try { if (!update) { // 準(zhǔn)備數(shù)據(jù)的流程(略) update = true; } readLock.lock(); } finally { writeLock.unlock(); } // 鎖降級完成,寫鎖降級為讀鎖 } try { // 使用數(shù)據(jù)的流程(略) } finally { readLock.unlock(); } }
上述示例中履磨,當(dāng)數(shù)據(jù)發(fā)生變更后蛉抓,
布爾
類型且volatile
修飾update
變量被設(shè)置為false
,此時所有訪問processData()
方法的線程都能夠感知到變化剃诅,但只有一個線程能夠獲取到寫鎖巷送,其他線程會被阻塞在讀鎖和寫鎖的lock()
方法上。當(dāng)前線程獲取寫鎖完成數(shù)據(jù)準(zhǔn)備之后矛辕,再獲取讀鎖笑跛,隨后釋放寫鎖,完成鎖降級聊品。鎖降級中讀鎖的獲取是否必要呢飞蹂?
答案是必要的。主要是為了 保證數(shù)據(jù)的可見性翻屈。
如果當(dāng)前線程不獲取讀鎖而是直接釋放寫鎖陈哑,假設(shè)此刻另一個線程(記作線程T
)獲取了寫鎖并修改了數(shù)據(jù),那么 當(dāng)前線程無法感知線程T
的數(shù)據(jù)更新伸眶。
如果當(dāng)前線程獲取讀鎖惊窖,即遵循鎖降級的步驟,則線程T
將會被阻塞赚抡,直到當(dāng)前線程使用數(shù)據(jù)并釋放讀鎖之后爬坑,線程T才能獲取寫鎖進行數(shù)據(jù)更新。RentrantReadWriteLock
不支持鎖升級涂臣。目的也是保證數(shù)據(jù)可見性盾计,如果讀鎖已被多個線程獲取售担,其中任意線程成功獲取了寫鎖并更新了數(shù)據(jù),則其更新對其他獲取到讀鎖的線程是不可見的署辉。
LockSupport工具
當(dāng)需要阻塞或喚醒一個線程的時候族铆,都會使用LockSupport
工具類來完成相應(yīng)工作。
LockSupport
定義了一組的公共靜態(tài)方法哭尝,這些方法提供了最基本的 線程阻塞和喚醒功能哥攘,而LockSupport
也成為構(gòu)建同步組件的基礎(chǔ)工具。
LockSupport
提供的 阻塞和喚醒的方法 如下:
-
park()
:阻塞當(dāng)前線程材鹦,只有調(diào)用unpark(Thread thread)
或者被中斷之后才能從park()
返回逝淹。 -
parkNanos(long nanos)
:再park()
的基礎(chǔ)上增加了超時返回。 -
parkUntil(long deadline)
:阻塞線程知道deadline
對應(yīng)的時間點桶唐。 -
park(Object blocker)
:Java 6
時增加栅葡,blocker
為當(dāng)前線程在等待的對象。 -
parkNanos(Object blocker, long nanos)
:Java 6
時增加尤泽,blocker
為當(dāng)前線程在等待的對象欣簇。 -
parkUntil(Object blocker, long deadline)
:Java 6
時增加,blocker
為當(dāng)前線程在等待的對象坯约。 -
unpark(Thread thread)
:喚醒處于阻塞狀態(tài)的線程thread
熊咽。
有對象參數(shù)的阻塞方法在線程dump
時,會有更多的現(xiàn)場信息闹丐。
Condition接口
任意一個Java對象横殴,都擁有一組監(jiān)視器方法,定義在java.lang.Object
)妇智,主要包括wait()
滥玷、wait(long timeout)
、notify()
以及notifyAll()
方法巍棱,這些方法與synchronized
同步關(guān)鍵字配合,可以實現(xiàn)等待/通知模式蛋欣。
Condition
接口也提供了類似Object
的監(jiān)視器方法航徙,與Lock
配合可以實現(xiàn) 等待/通知 模式,但是這兩者在使用方式以及功能特性上還是有差別的陷虎。
以下是Object的監(jiān)視器方法與Condition接口的對比:
對比項 | Object | Condition |
---|---|---|
前置條件 | 獲取對象的鎖 | 調(diào)用Lock.lock() 獲取鎖到踏;調(diào)用Lock.newCondition() 獲取condition對象 |
調(diào)用方式 | object.wait() |
condition.wait() |
等待隊列個數(shù) | 一個 | 多個 |
當(dāng)前線程釋放鎖并進入等待狀態(tài) | 支持 | 支持 |
當(dāng)前線程釋放鎖并進入等待狀態(tài),在等待狀態(tài)中不響應(yīng)中斷 | 不支持 | 支持 |
當(dāng)前線程釋放鎖并進入超時等待狀態(tài) | 支持 | 支持 |
當(dāng)前線程釋放鎖并進入等待狀態(tài)到將來某時間 | 不支持 | 支持 |
喚醒等待隊列的一個線程 | 支持 | 支持 |
喚醒等待隊列的全部線程 | 支持 | 支持 |
Condition接口與示例
Condition
定義了等待/通知兩種類型的方法尚猿,當(dāng)前線程調(diào)用這些方法時窝稿,需要提前獲取到Condition
對象關(guān)聯(lián)的鎖。
Condition
對象是由調(diào)用Lock
對象的newCondition()
方法創(chuàng)建出來的凿掂,換句話說伴榔,Condition
是依賴Lock
對象的纹蝴。
Condition
的使用方式比較簡單,需要注意在調(diào)用方法前獲取鎖踪少,如下:
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
public void conditionWait() throws InterruptedException {
lock.lock();
try {
condition.await();
} finally {
lock.unlock();
}
}
public void conditionSignal() throws InterruptedException {
lock.lock();
try {
condition.signal();
} finally {
lock.unlock();
}
}
Condition
接口方法介紹:
-
void await() throws InterruptedException
: 當(dāng)前線程進入等待狀態(tài)直到被通知或中斷 -
void awaitUninterruptibly()
:當(dāng)前線程進入等待狀態(tài)直到被通知塘安,對中斷不敏感 -
long awaitNanos(long var1) throws InterruptedException
:當(dāng)前線程進入等待狀態(tài)直到被通知、中斷或超時 -
boolean await(long var1, TimeUnit var3) throws InterruptedException
:當(dāng)前線程進入等待狀態(tài)直到被通知援奢、中斷或超時 -
boolean awaitUntil(Date var1) throws InterruptedException
:當(dāng)前線程進入等待狀態(tài)直到被通知兼犯、中斷或到某一時間 -
void signal()
:喚醒Condition
上一個在等待的線程 -
void signalAll()
:喚醒Condition
上全部在等待的線程
獲取一個Condition必須通過Lock的newCondition()方法。
通過下面這個有界隊列的示例我們來深入了解下 Condition
的使用方式:
public class BoundedQueue<T> {
private Object[] items;
// 添加的下標(biāo)集漾,刪除的下標(biāo)和數(shù)組當(dāng)前數(shù)量
private int addIndex, removeIndex, count;
private Lock lock = new ReentrantLock();
private Condition notEmpty = lock.newCondition();
private Condition notFull = lock.newCondition();
public BoundedQueue(int size) {
items = new Object[size];
}
// 添加一個元素切黔,如果數(shù)組滿,則添加線程進入等待狀態(tài)具篇,直到有"空位"
public void add(T t) throws InterruptedException {
lock.lock();
try {
while (count == items.length){
notFull.await();
}
items[addIndex] = t;
if (++addIndex == items.length)
addIndex = 0;
++count;
notEmpty.signal();
} finally {
lock.unlock();
}
}
// 由頭部刪除一個元素纬霞,如果數(shù)組空,則刪除線程進入等待狀態(tài)栽连,直到有新添加元素
@SuppressWarnings("unchecked")
public T remove() throws InterruptedException {
lock.lock();
try {
while (count == 0)
notEmpty.await();
Object x = items[removeIndex];
if (++removeIndex == items.length)
removeIndex = 0;
--count;
notFull.signal();
return (T) x;
} finally {
lock.unlock();
}
}
}
上述代碼add
和 remove
方法 都需要先獲取鎖保證數(shù)據(jù)的可見性和排它性险领。
當(dāng)儲存數(shù)組滿了的時候時候調(diào)用notFull.await()
,線程即釋放鎖并進入等待隊列秒紧。
當(dāng)儲存數(shù)組未滿時绢陌,則添加到數(shù)組,并通知 notEmpty
中等待的線程熔恢。
方法中使用while
循環(huán)是為了防止過早或者意外的通知脐湾。
Condition的實現(xiàn)分析
主要包括 等待隊列、等待和通知叙淌。
-
等待隊列
等待隊列是一個FIFO
的隊列秤掌,在隊列中的每個節(jié)點都包含了一個線程引用,該線程就是在Condition
對象上等待的線程鹰霍,如果一個線程調(diào)用了Condition.await()
方法闻鉴,那么該線程將會釋放鎖、構(gòu)造成節(jié)點加入等待隊列并進入等待狀態(tài)茂洒。同步隊列和等待隊列中節(jié)點類型都是同步器的靜態(tài)內(nèi)部類AbstractQueuedSynchronizer.Node
孟岛。線程調(diào)用
Condition.await()
,即以當(dāng)前線程構(gòu)造節(jié)點督勺,并加入等待隊列的尾部渠羞。
等待隊列的基本結(jié)構(gòu)如下圖所示,
Condition
的實現(xiàn)是同步器的內(nèi)部類智哀,因此每個Condition
實例都能夠訪問同步器提供的方法次询,相當(dāng)于每個Condition
都擁有所屬同步器的引用。
同步隊列與等待隊列 -
等待
調(diào)用Condition
的await()
等方法瓷叫,會使當(dāng)前線程進入等待隊列并釋放鎖屯吊,同時線程狀態(tài)變?yōu)榈却隣顟B(tài)送巡。當(dāng)從await()
方法返回時,當(dāng)前線程一定獲取了Condition
相關(guān)聯(lián)的鎖雌芽。Condition
的await()
源碼:public final void await() throws InterruptedException { if (Thread.interrupted()) { throw new InterruptedException(); } else { AbstractQueuedSynchronizer.Node node = this.addConditionWaiter(); int savedState = AbstractQueuedSynchronizer.this.fullyRelease(node); int interruptMode = 0; while(!AbstractQueuedSynchronizer.this.isOnSyncQueue(node)) { LockSupport.park(this); if ((interruptMode = this.checkInterruptWhileWaiting(node)) != 0) { break; } } if (AbstractQueuedSynchronizer.this.acquireQueued(node, savedState) && interruptMode != -1) { interruptMode = 1; } if (node.nextWaiter != null) { this.unlinkCancelledWaiters(); } if (interruptMode != 0) { this.reportInterruptAfterWait(interruptMode); } } }
調(diào)用該方法的線程成功獲取了鎖的線程授艰,也就是同步隊列中的首節(jié)點,該方法會將當(dāng)前線程構(gòu)造成節(jié)點并加入等待隊列中世落,然后釋放同步狀態(tài)淮腾,喚醒同步隊列中的后繼節(jié)點,然后當(dāng)前線程會進入等待狀態(tài)屉佳。
當(dāng)?shù)却犃兄械墓?jié)點被喚醒谷朝,則喚醒節(jié)點的線程開始嘗試獲取同步狀態(tài)。如果不是通過其他線程調(diào)用Condition.signal()
方法喚醒武花,而是對等待線程進行中斷圆凰,則會拋出InterruptedException
。
當(dāng)前線程加入等待隊列 -
通知
調(diào)用Condition
的signal()
方法体箕,將會喚醒在等待隊列中等待時間最長的節(jié)點(首節(jié)點)专钉,在喚醒節(jié)點之前,會將節(jié)點移到同步隊列中累铅。Condition
的signal()
源碼:public final void signal() { if (!AbstractQueuedSynchronizer.this.isHeldExclusively()) { throw new IllegalMonitorStateException(); } else { AbstractQueuedSynchronizer.Node first = this.firstWaiter; if (first != null) { this.doSignal(first); } } } private void doSignal(AbstractQueuedSynchronizer.Node first) { do { if ((this.firstWaiter = first.nextWaiter) == null) { this.lastWaiter = null; } var1.nextWaiter = null; } while (!AbstractQueuedSynchronizer.this.transferForSignal(first) && (first = this.firstWaiter) != null); }
調(diào)用該方法的前置條件是當(dāng)前線程必須獲取了鎖跃须,可以看到
signal()
方法進行了isHeldExclusively()
檢查,也就是當(dāng)前線程必須是獲取了鎖的線程娃兽。
接著獲取等待隊列的首節(jié)點菇民,將其移動到同步隊列并使用LockSupport
喚醒節(jié)點中的線程。
節(jié)點從等待隊列移動到同步隊列通過調(diào)用同步器的
enq(Node node)
方法投储,等待隊列中的頭節(jié)點線程安全地移動到同步隊列第练。
當(dāng)節(jié)點移動到同步隊列后,當(dāng)前線程再使用LockSupport
喚醒該節(jié)點的線程玛荞。被喚醒后的線程娇掏,將從
await()
方法中的while
循環(huán)中退出isOnSyncQueue(Node node)
方法返回true
,節(jié)點已經(jīng)在同步隊列中勋眯,進而調(diào)用同步器的acquireQueued()
方法加入到獲取同步狀態(tài)的競爭中驹碍。成功獲取同步狀態(tài)之后,被喚醒的線程將從先前調(diào)用的
await()
方法返回凡恍,此時該線程已經(jīng)成功地獲取了鎖。Condition
的signalAll()
方法怔球,相當(dāng)于對等待隊列中的每個節(jié)點均執(zhí)行一次signal()方法嚼酝,效果就是將等待隊列中所有節(jié)點全部移動到同步隊列中,并喚醒每個節(jié)點的線程竟坛。
小結(jié)
-
Lock
接口提供的方法lock()
闽巩、unlock()
等獲取和釋放鎖的介紹 - 隊列同步器的使用 以及 自定義隊列同步器
- 重入鎖 的使用和實現(xiàn)介紹
- 讀寫鎖 的 讀鎖 和 寫鎖
-
LockSupport
工具實現(xiàn) 阻塞和喚醒線程 -
Condition
接口實現(xiàn) 等待/通知模式
以上
</article>