目前在Java語言層面能實現阻塞喚醒的方式一共有三種:suspend與resume組合、wait與notify組合、park與unpark組合。
其中suspend與resume因為存在無法解決的竟態問題而被Java廢棄,同樣,wait與notify也存在竟態條件,wait必須在notify之前執行,假如一個執行緒先執行notify再執行wait將可能導致一個執行緒永遠阻塞,如此一來,必須要提出另外一種解決方案,就是park與unpark組合,它位於JDK的juc包下,應該也是因為當時編寫juc時發現java現有方式無法解決問題而引入的新阻塞喚醒方式,由於park與unpark使用的是許可機制,許可最大為1,所以unpark與park操作不會累加,而且unpark可以在park之前執行,如unpark先執行,後面park將不阻塞。
Java真正意義上的語言層面上的併發程式設計應該從併發專家Doug Lea領導的JSR-166開始,此規範請求向JCP提交了向Java語言中新增併發程式設計工具,即在jdk中新增java.util.concurrent工具包供開發者使用,開發者可以輕鬆構建自己的同步器,而在此之前併發過程中同步都只能依靠JVM內建的管程。
JDK的併發包中最重要的一個框架就是ASQ框架,它的阻塞和喚醒使用的是LockSupport類的park與unpark方法,分別呼叫的是Unsafe類的park與unpark本地方法。邏輯如下:
阻塞
if(嘗試獲取鎖失敗) {
建立node
使用CAS方式把node插入到佇列尾部
while(true){
if(嘗試獲取鎖成功 並且 node的前驅節點為頭節點){
把當前節點設定為頭節點
跳出迴圈
}else{
使用CAS方式修改node前驅節點的waitStatus標識為signal
if(修改成功)
LockSupport.park();
}
}
}複製程式碼
喚醒
if(嘗試釋放鎖成功){
LockSupport.unpark(下一節點包含的執行緒);
}複製程式碼
假如一條執行緒參與鎖競爭,首先先嚐試獲取鎖,失敗的話建立節點並插入佇列尾部,然後再次嘗試獲取鎖,如若成功則不做其他任務處理直接返回,否則設定節點狀態為待執行狀態,最後使用LockSupport的park阻塞當前執行緒。前驅節點執行完後將嘗試喚醒後繼節點,使用的即是LockSupport的unpark喚醒。
總的來說,JDK提供的併發工具,在阻塞與喚醒操作方面由於suspend與resume存在各種各樣問題,必須使用LockSupport中提供的方法操作。
相關閱讀:
從JDK原始碼角度看併發競爭的超時
從 JDK 原始碼角度看 Java 併發的公平性
歡迎關注: