併發程式設計基礎 - 管程模型和synchronized原子性
通過上一篇我們知道通過記憶體屏障,在反編譯的組合語言中我們看到基於lock cmpxchg和lock addl 前置指令解決了synchronized和volatile的可見性和有序性問題。也在反彙編中看到了synchronized中的monitorenter和monitorexit,這是synchronized實現原子性的關鍵。monitor可以叫監視器,也可以翻譯為管程,並不是java特有的產物。併發過程中除了有執行緒原子性的問題,我們還需要解決執行緒間的互斥還得資料同步。
在解決併發問題的歷史過程中,最早是使用訊號量處理,在juc包中Semaphore還有其實現支援,將在編髮程式設計工具中分析。往後才是管程模型的提出,但是管程和訊號量是等價的,即管程能實現的功能(或效果)訊號量也能實現。在java中synchronized就是管程模型的實現,只是有c語言實現不方便檢視,但是在JDK5之後juc(java.util.concurrent)包下面的API就是基於volatile(解決了可見性和有序性)+ CAS 模擬管程實現。實現的核心由AQS(AbstractQueuedSynchronizer)實現,CAS使用sun.misc.UnSafe的CAS相關API實現。所以當前先使用ReentrantLock和Condition模擬管程實現【先大概看看管程是什麼東西】,實現執行緒安全的原子性,即操作的過程對外不可見,外面不能看見執行中的中間狀態,那麼管程的思想就是執行的過程封裝起來,滿足條件後再喚醒其他(執行緒)操作,也就解決了原子性。
/**
* 使用{@link ReentrantLock} 和 {@link Condition} 模擬管程模型;在synchronized中只能有一個條件佇列,
* 而當前可以建立多個Condition即可
*
* <p>
* 對於入隊操作,如果佇列已滿,就需要等待直到佇列不滿,所以這裡用了notFull.await();。
* 對於出隊操作,如果佇列為空,就需要等待直到佇列不空,所以就用了notEmpty.await();。
* 如果入隊成功,那麼佇列就不空了,就需要通知條件變數:佇列不空notEmpty對應的等待佇列。
* 如果出隊成功,那就佇列就不滿了,就需要通知條件變數:佇列不滿notFull對應的等待佇列。
*
* <p>
* 我們知道 {@link Object#wait()}、{@link Object#notify()}、{@link Object#notifyAll()} 只能在 synchronized中使用
* 同樣 {@link Condition#await()}、{@link Condition#signal()}、{@link Condition#signalAll()} 只能在Lock&Condition中使用
* 並且這三個方法一一對應, 需要注意上面三個是{@link Object}的方法,所以在下面的{@link Condition}中同樣存在,千萬別呼叫錯了
*
* @author kevin
* @date 2020/7/29 23:51
* @since 1.0.0
*
* @see LinkedBlockingQueue#takeLock
* @see LinkedBlockingQueue#putLock
*
* @see LinkedBlockingQueue#notFull
* @see LinkedBlockingQueue#notEmpty
*/
public class BlockedQueue {
/** 建立一個公平鎖 */
final Lock lock = new ReentrantLock(true);
/** 條件變數:佇列不滿 */
final Condition notFull = lock.newCondition();
/** 條件變數:佇列不空 */
final Condition notEmpty = lock.newCondition();
/**
* 入隊操作
*/
void enqueue() throws InterruptedException {
lock.lock();
try {
while (佇列已滿) {
// 等待佇列不滿
notFull.await();
}
// 省略入隊操作
// 入隊後通知可出隊
notEmpty.signal();
} finally {
lock.unlock();
}
}
void dequeue() throws InterruptedException {
lock.lock();
try {
while (佇列已空) {
// 等待佇列不空
notEmpty.await();
}
// 省略出佇列操作,省略一萬行
// 出佇列後,通知可入隊
notFull.signal();
} finally {
lock.unlock();
}
}
}
管程模型在管程模型分為三種(Java選擇了MESA管程模型):
1)、Hasen模型
Hasen模型要求notify放到最後,這樣T2執行緒通知T1後,T2執行緒就結束了,然後T1執行完,這樣就能保證同一時刻只有一個執行緒在執行。
2)、Hoare模型
Hoare模型裡面,T2執行緒通知完T1執行緒後,T2馬上阻塞,T1馬上執行;等T1執行完之後再喚醒T2執行緒,也能保證同一時刻只有一個執行緒在執行,但是T2多了一次阻塞喚醒操作。
3)、MESA管程模型(Java使用MESA模型實現)
MESA模型中,T2喚醒T1之後,T2還是會接著執行,T1並不立即執行,僅僅是從條件變數佇列到等待佇列中。
好處:notify(或notifyAll)、signal(或signalAll)不用放到程式碼的最後,T2也沒有多餘的阻塞喚醒操作
壞處:T1執行的時候,可能曾經滿足過條件,現在已經不能滿足了,需要增加迴圈驗證條件方式(個人理解也算是樂觀鎖的思想)。
MESA管程模型:
看到上圖的Java MESA管程模型,就理解synchronized與看似從天而降的的Object的 wait、notify、notifyAll方法,synchronized可以修飾
1、方法塊前提是括號中需要有物件即Object
2、修飾普通方法,修飾的是 Object物件
3、修飾靜態方法,修飾的是 .class,可以理解為也是Object物件
所以synchronized關鍵字,處理的是Object物件(其子類)的物件頭的狀態,後續再詳細分析。上中的佇列模型即反彙編之後看到的monitorenter、monitorexit,底層封裝了條件滿足後,對佇列中的 wait(等待)、notify(notifyAll 喚醒)操作。結合c底層的對應的佇列,和管程物件,可以理解為下圖:
注意:
除非經過深思熟慮(或者有非常的把握)儘量使用Object的 notifyAll(不要使用notify)、Condition的 signalAll(不要使用signal),除非滿足以下三個條件【否則可能照成某些執行緒的飢餓等】:
1)、所以等待執行緒擁有相同的等待條件
2)、所以等待執行緒被喚醒後,執行相同的操作
3)、只需要喚醒一個執行緒
相關文章
- 併發程式設計基礎與原子操作程式設計
- 併發程式設計的原子性 != 事務ACID的原子性程式設計
- Java併發程式設計-併發程式設計的Bug源頭:可見性、原子性和有序性問題Java程式設計
- 併發程式設計 synchronized程式設計synchronized
- 併發程式設計之:synchronized程式設計synchronized
- Java併發程式設計:synchronizedJava程式設計synchronized
- Java併發程式設計之synchronizedJava程式設計synchronized
- Java併發程式設計-synchronized指南Java程式設計synchronized
- Java併發程式設計Bug源頭:可見性、原子性和有序性問題Java程式設計
- Golang併發程式設計基礎Golang程式設計
- 併發程式設計基礎(上)程式設計
- 併發程式設計基礎(下)程式設計
- Java併發程式設計基礎Java程式設計
- Go併發程式設計基礎Go程式設計
- 理解併發程式設計的幾種"性" -- 可見性,有序性,原子性程式設計
- JVM 併發性: Java 和 Scala 併發性基礎JVMJava
- 併發程式設計的鎖機制:synchronized和lock程式設計synchronized
- 併發程式設計——基礎概念(一)程式設計
- 併發程式設計——基礎概念(二)程式設計
- Java併發程式設計之原子變數Java程式設計變數
- 併發程式設計基礎——JMM簡介程式設計
- Go 併發程式設計 - Goroutine 基礎 (一)Go程式設計
- 併發程式設計之synchronized(二)------jvm對synchronized的優化程式設計synchronizedJVM優化
- 併發程式設計---JMM模型程式設計模型
- 鴻蒙程式設計江湖:併發程式設計基礎與鴻蒙中的任務併發鴻蒙程式設計
- 併發程式設計——synchronized關鍵字的使用程式設計synchronized
- Java併發程式設計:Synchronized及其實現原理Java程式設計synchronized
- 併發程式設計之多執行緒基礎程式設計執行緒
- Java併發程式設計——基礎知識(一)Java程式設計
- Java併發程式設計-執行緒基礎Java程式設計執行緒
- Java併發程式設計——基礎知識(二)Java程式設計
- Go併發程式設計之原子操作sync/atomicGo程式設計
- C++11併發程式設計:原子操作atomicC++程式設計
- 《java併發程式設計的藝術》原子操作類Java程式設計
- 併發程式設計模型小結程式設計模型
- JVM併發程式設計模型覽JVM程式設計模型
- 併發程式設計原理學習:synchronized關鍵字程式設計synchronized
- 《JAVA併發程式設計實戰》原子變數和非阻塞同步機制Java程式設計變數