關於synchronized的Monitor Object機制的研究
這是一道面試題
- Synchronized 介紹
- monitor Object 設計模式
- Monitor(java多執行緒同步機制)
- Refer
synchronized介紹
synchronized關鍵字通過修飾一個方法或宣告一個程式碼塊,從而產生一個同步物件鎖以及對應的同步程式碼塊。
每當有執行緒要對該同步程式碼塊進行訪問時,執行緒就會首先嚐試去獲取該物件鎖,並在成功獲取到物件鎖後,對該同步程式碼塊進行正常訪問,在同步程式碼塊訪問過程中,執行緒會一直持有該物件鎖直到同步程式碼塊訪問完畢才會釋放。
在上述執行緒持有同步鎖並進行同步程式碼塊訪問過程中,其它執行緒將無法獲得該物件鎖,也無法訪問該同步程式碼,這些執行緒都會被阻塞直到上述執行緒訪問完畢。syschronized關鍵字,通過以上措施,確保每次只有一個執行緒能持有物件鎖並對同步程式碼塊進行訪問,並在訪問結束之前,不會有其它執行緒對其進行訪問。
也就說,即使同步程式碼塊在執行過程中遭遇執行緒排程,其它執行緒也無法訪問該同步程式碼塊,直到該執行緒被重新排程並完成同步程式碼塊的訪問並釋放物件鎖。
這樣就保證了執行緒對同步程式碼塊訪問的連續性不受執行緒排程而中斷。
synchronized被稱為重量級的鎖,它的同步包括:
- 對於普通方法同步,鎖是當前例項物件
- 對於靜態方法同步,鎖是當前類的 Class 物件
- 對於方法塊同步,鎖是 Synchronized 括號裡的物件
上述都是物件級別的鎖,當一個執行緒訪問物件中的同步方法時,會獲取到物件級別的鎖,由於 Synchronized 內部是可重入的互斥鎖,所以執行緒可再次重入用 Synchronized 修飾的方法,但當其它執行緒執行同一個物件的帶有 Synchronized 的方法時,會被阻塞,即使和以持有物件鎖的執行緒執行的相同物件的不同 Synchronized 方法。因為鎖是物件級別的。比如執行緒 A、B。物件 Foo 有同步方法 M、N。執行緒 A 首先執行同步方法 M 時就會獲取物件鎖,此時 B 不能執行同一把物件鎖修飾的方法 M、N。除非 A 釋放鎖。又因為鎖是可重入的,所以 A 可以繼續執行 M,N 方法。可重入鎖一定程度上避免了死鎖的問題,內部是關聯一個計數器,加一次鎖計數器值加一,為零時釋放鎖。
如何理解鎖是“物件”?
Java 程式語言中號稱一切皆物件。當我們 new 一個物件的時候 JVM 會給 heap 中分配物件。如下圖:
(物件頭)這個頭包括兩個部分,第一部分用於儲存自身執行時的資料例如GC標誌位、雜湊碼、鎖狀態 等資訊。第二部分存放指向方法區類靜態資料的指標。鎖狀態就是用來同步操作的 bit 位。因為鎖資訊是儲存在物件上的,所以就不難理解 鎖是物件 這句話了。
那麼 Java 為什麼要將 鎖 內建到物件中呢?這要從 monitor Object 設計模式說起。
monitor Object 設計模式
問題描述:
我們在開發併發的應用時,經常需要設計這樣的物件,該物件的方法會在多執行緒的環境下被呼叫,而這些方法的執行都會改變該物件本身的狀態。為了防止競爭條件 (race condition) 的出現,對於這類物件的設計,需要考慮解決以下問題:
- 在任一時間內,只有唯一的公共的成員方法,被唯一的執行緒所執行。
- 對於物件的呼叫者來說,如果總是需要在呼叫方法之前進行拿鎖,而在呼叫方法之後進行放鎖,這將會使併發應用程式設計變得更加困難。
- 如果一個物件的方法執行過程中,由於某些條件不能滿足而阻塞,應該允許其它的客戶端執行緒的方法呼叫可以訪問該物件。
我們使用 Monitor Object 設計模式來解決這類問題:將被客戶執行緒併發訪問的物件定義為一個 monitor 物件。客戶執行緒僅能通過 monitor 物件的同步方法才能使用 monitor 物件定義的服務。為了防止陷入競爭條件,在任一時刻只能有一個同步方法被執行。每一個 monitor 物件包含一個 monitor 鎖,被同步方法用於序列訪問物件的行為和狀態。此外,同步方法可以根據一個或多個與 monitor 物件相關的 monitor conditions 來決定在何種情況下掛起或恢復他們的執行。
結構
在 Monitor Object 模式中,主要有四種類型的參與者:
- 監視者物件 (Monitor Object): 負責定義公共的介面方法,這些公共的介面方法會在多執行緒的環境下被呼叫執行。
- 同步方法:這些方法是監視者物件所定義。為了防止競爭條件,無論是否同時有多個執行緒併發呼叫同步方法,還是監視者物件含有多個同步方法,在任一時間內只有監視者物件的一個同步方法能夠被執行。
- 監視鎖 (Monitor Lock): 每一個監視者物件都會擁有一把監視鎖。
- 監視條件 (Monitor Condition): 同步方法使用監視鎖和監視條件來決定方法是否需要阻塞或重新執行。
執行序列圖
在監視者物件模式中,在參與者之間將發生如下的協作過程:
1、同步方法的呼叫和序列化。當客戶執行緒呼叫監視者物件的同步方法時,必須首先獲取它的監視鎖。只要該監視者物件有其他同步方法正在被執行,獲取操作便不會成功。在這種情況下,客戶執行緒將被阻塞直到它獲取監視鎖。當客戶執行緒成功獲取監視鎖後,進入臨界區,執行方法實現的服務。一旦同步方法完成執行,監視鎖會被自動釋放,目的是使其他客戶執行緒有機會呼叫執行該監視者物件的同步方法。
2、同步方法執行緒掛起。如果呼叫同步方法的客戶執行緒必須被阻塞或是有其他原因不能立刻進行,它能夠在一個監視條件上等待,這將導致該客戶執行緒暫時釋放監視鎖,並被掛起在監視條件上。
3、監視條件通知。一個客戶執行緒能夠通知一個監視條件,目的是為了讓一個前期使自己掛起在一個監視條件上的同步方法執行緒恢復執行。
4、同步方法執行緒恢復。一旦一個早先被掛起在監視條件上的同步方法執行緒獲取通知,它將繼續在最初的等待監視條件的點上執行。在被通知執行緒被允許恢復執行同步方法之前,監視鎖將自動被獲取。圖中描述了監視者物件的動態特性。
其實, monitor object 設計模式執行時序圖中的紅線部分 Monitor Object、Monitor Lock、Monitor Condition 三者就是 Java Object!! Java 將該模式內建到語言層面,物件加 Synchronized 關鍵字,就能確保任何對它的方法請求的同步被透明的進行,而不需要呼叫者的介入。
這也就是為什麼 Java 所有物件的基類 Object 中會有 wait()、notify()、notifyAll() 方法了。
Monitor(java多執行緒同步機制)
為了達到同步,java在一個監控器(Monitor)的基礎上實現了一個巧妙的方案。
監控器是一個控制機制,可以認為是一個很小的、只能容納一個執行緒的盒子。
一旦一個執行緒進入監控器,其它的執行緒必須等待,直到那個執行緒退出監控為止。
通過這種方式,一個監控器可以保證共享資源在同一時刻只可被一個執行緒使用,這種方式稱為同步。
一旦一個執行緒進入一個例項的任何同步方法,別的執行緒將不能進入該同一例項的其它同步方法,但是該例項的非同步方法仍然能夠被呼叫。