1. 初識
synchronized 是 Java 中的關鍵字,是一種 同步鎖 ,可重入鎖,悲觀鎖。它修飾的對象有以下幾種:
具體表現為以下3種形式。
對于普通同步方法,鎖是當前實例對象。
對于靜態同步方法,鎖是當前類的 Class 對象。
對于同步方法塊,鎖是 synchonized 括號里配置的對象。
雖然可以使用 synchronized 來定義方法,但 synchronized 并不屬于方法定義的一部分,因此,synchronized
關鍵字不能被繼承。 如果在父類中的某個方法使用了 synchronized 關鍵字,而在子類中覆蓋了這個方法,在子類中的這
個方法默認情況下并不是同步的,而必須顯式地在子類的這個方法中加上 synchronized
關鍵字才可以。當然,還可以在子類方法中調用父類中相應的方法,這樣雖然子類中的方法本身不是同步的,但子類調用了父類的同步方法,因此,子類的方法也就相當于同步了。
如果一個代碼塊被 synchronized 修飾了,當一個線程獲取了對應的鎖,并執行該代碼塊時,其他線程便只能一直等待,等待持有鎖的線程釋放鎖,而這里獲取鎖的線程釋放鎖只會有兩種情況:
- 獲取鎖的線程執行完了該代碼塊,然后線程釋放對鎖的占有;
- 線程執行發生異常,此時 JVM 會讓線程自動釋放鎖。
那么如果這個獲取鎖的線程由于要等待 IO 或者其他原因(比如調用 sleep
方法)被阻塞了,但是又沒有釋放鎖,其他線程便只能干巴巴地等待,試想一下,這多么影響程序執行效率。
因此就需要有一種機制可以不讓等待的線程一直無期限地等待下去(比如只等待一定的時間或者能夠響應中斷),通過 Lock 就可以辦到。
2. synchronized 的底層字節碼
synchronized 三種用法及其原理
2.1 同步代碼塊
通過反編譯可以看到, 其實現使用的是 monitorenter 和 monitorexit 指令。
2.2 普通同步方法
調用指令將會檢查方法的 ACC_SYNCHRONIZED 訪問標志是否被設置。如果設置了,執行線程會將先持有 monitor 然后再執行方法,最后在方法完成(正常和異常情況都算完成)時釋放 monitor。
2.3 類同步方法
ACC_STATIC 和 ACC_SYNCHRONIZED 訪問標志區分該方法是否是靜態同步方法。
3. synchronized 鎖的是什么
synchronized 用的鎖是存在 Java 對象頭里的。
3.1 什么是管程
管程 (英語:Monitors,也稱為監視器) 是一種程序結構,結構內的多個子程序(對象或模塊)形成的多個工作線程互斥訪問共享資源。
這些共享資源一般是硬件設備或一群變量。對共享變量能夠進行的所有操作集中在一個模塊中。(把信號量及其操作原語“封裝”在一個對象內部)管程實現了在一個時間點,最多只有一個線程在執行管程的某個子程序。管程提供了一種機制,管程可以看做一個軟件模塊,它是將共享的變量和對于這些共享變量的操作封裝起來,形成一個具有一定接口的功能模塊,進程可以調用管程來實現進程級別的并發控制。
3.2 monitor ObjectMonitor
在 HotSpot 虛擬機中, monitor 采用 ObjectMonitor 實現
ObjectMonitor.java ->> ObjectMonitor.cpp ->> ObjectMonitor.hpp
Java 中的每個對象天生都帶著一個對象的監視器(所以 Java 中任何一個對象都可以成為一個鎖)。
任何對象都有一個 monitor 與之關聯,當且一個 monitor 被持有后,它將處于鎖定狀態。線程執行到 被 synchronized 修飾的方法時,將會 嘗試獲取對象頭的 monitor 的所有權,即嘗試獲得對象的鎖。