天天看點

java同步鎖(synchronized)機制再總結

之前在《對java多線程的線程安全性的一些總結》中對java多線程的線程安全機制做了大體上的總結,今天再對線程安全機制中的同步鎖(synchronized)機制再做深入點的總結。

Java中同步鎖(synchronized)根據使用場景可以分成三類:方法鎖,對象鎖,類鎖

舉例子比較直覺

方法鎖:

public synchronized  void fun1()

    {

       System.out.println("fun1test");

    }

--上面例子就是典型的方法鎖,直接在方法名前用synchronized進行修飾

對象鎖:

Object obj=new Object();

public void  fun2()

    {

       synchronized(obj){

       System.out.println("fun1test");

       }

    }

--使用synchronized(obj)的形式實作同步機制,每個線程執行到該同步語句塊時,先去或取obj的互斥鎖,擷取到則執行否則等待,前面線程執行完後釋放鎖

類鎖:

public void fun3()

    {

       synchronized(SynchronizedTest.class)

       {

           System.out.println("fun3test");

       }

    }

--使用synchronized(SynchronizedTest.class)的形式實作同步機制,其實這個是比較常見的類鎖的形式,如果用synchronized來修飾靜态成員方法或者synchronized(靜态變量),其實這種也算類鎖。看到這裡你大概會有點明白synchronized鎖的所謂“分類”。

    其實根本沒有所謂的方法鎖,對象鎖,類鎖,是的你沒聽錯,synchronized鎖本質上不存在什麼方法鎖,對象鎖,類鎖的差別,因為它們本質上全是“對象鎖”,所謂的方法鎖,比如例子一,實際上是synchronized(this),也就是說調用這個成員方法的對象是用它的互斥鎖來實作同步互斥機制的。而所謂的類鎖,比如例子三,不管它修飾靜态方法,還是synchronized(SynchronizedTest.class)的形式,本質上它也是“對象鎖”,隻是這個對象有點特殊,它是類對象,是的,在java中,存在類對象。這點是學C++的人比較費解的地方,因為在C++中從來沒有 聽過類對象這個說法,為什麼呢?這個跟java和C++的連結機制的差異有關系,C++是靜态連結的,是以在程式的記憶體中不需要儲存完整的類定義的資訊,而java是動态連結的,為了實作動态連結,java必須在加載.class檔案後儲存類定義的完整資訊,這樣類就被虛拟機配置設定了一份記憶體用來儲存類的資訊,而這塊記憶體就是類對象了。

    說到這裡就不得不提到一個概念,就是“對象的互斥鎖”,這是什麼玩意,一開始我以為是所有java類的最頂層的父類Object的一個成員屬性,但是去檢視Object的源碼,發現并沒有這個東西,後來發現對象鎖是java虛拟機實作的,簡單來說就是每個對象在虛拟機中都對應于一個“對象鎖”,這個對象鎖由虛拟機來維護,至于對象和對象鎖之間的映射關系是怎麼實作的還不大清楚,有可能是每個對象的記憶體中儲存一分對象鎖的指針或者在每個對象建立中虛拟機填充了一個一個互斥鎖對象。任何線程要執行對synchronized保護的語句塊時,都要擷取到synchronized(obj)中對象obj的鎖。擷取到該對象鎖的線程可以多少通路和執行改語句塊,每進入一次,虛拟機在該對象鎖中加一,每執行完一次虛拟機在該對象鎖中減一。當對象鎖值為0時,其他線程才能擷取鎖進去通路。

這點很類似pv元語,是的,其實synchronized保護的語句塊在編譯時,會在語句塊前後分别插入monitorenter和moniterexist指令,非常類似pv元語的格式,這樣就能把這些和我們大學時學的pv元語鎖機制聯系起來了。

至于虛拟機具體是如何實作鎖機制的内容還需進一步探索。

--補充

上面關于類對象的說法有點不正确,雖然類的資訊儲存在方法區中,但是類對象是儲存在堆區中的,隻是類對象也有一份指向方法區中類資訊的指針。