Java Synchronized及實現原理

來源:互聯網
上載者:User

標籤:tee   trie   擷取   soc   pre   locking   衝突   其他   nts   

Synchronized是Java中解決並發問題的一種最常用的方法,也是最簡單的一種方法。Synchronized的作用主要有三個:(1)確保線程互斥的訪問同步代碼(2)保證共用變數的修改能夠及時可見(3)有效解決重排序問題。從文法上講,Synchronized總共有三種用法:

  (1)修飾普通方法

  (2)修飾靜態方法

  (3)修飾代碼塊

 

首先來看一下沒有使用同步的情況
public class SynchronizedTest {    public void method1(){        System.out.println("method1 start");        try{            System.out.println("method1 excute");            Thread.sleep(3000);        }catch (Exception e){            e.printStackTrace();        }        System.out.println("method1 end");    }    public void method2(){        System.out.println("method2 start");        try{            System.out.println("method2 excute");            Thread.sleep(1000);        }catch (Exception e){            e.printStackTrace();        }        System.out.println("method2 end");    }    public static void main(String[] args){        final SynchronizedTest test=new SynchronizedTest();        new Thread(new Runnable() {            @Override            public void run() {                test.method1();            }        }).start();        new Thread(new Runnable() {            @Override            public void run() {                test.method2();            }        }).start();    }}

輸出結果為:(線程1和線程2同時進入執行狀態,線程2執行速度比線程1快,所以線程2先執行完成,這個過程中線程1和線程2是同時執行的。)

method1 start
method1 excute
method2 start
method2 excute
method2 end
method1 end

 

使用synchronized修飾普通方法,其他保持不變,輸出結果為

method1 start
method1 excute
method1 end
method2 start
method2 excute
method2 end

可以很明顯的看出,線程2需要等待線程1的method1執行完成才能開始執行method2方法。為什嗎? 因為使用synchronized關鍵字修飾方法,則鎖為this,即調用method的類的執行個體test。

如果在main方法中增加一個

final SynchronizedTest test1=new SynchronizedTest();
並且使用test1調用method2,則輸出結果如下:

method1 start
method1 excute
method2 start
method2 excute
method2 end
method1 end

為什嗎? 因為雖然使用了關鍵字synchronized,但是調用synchronized的對象不一致,也就是synchronized鎖的不是同一個對象,所以同步並未生效。

 

 

使用synchronize修飾static方法(即將方法修改為static synchronized,其他不變),輸出結果為

method1 start
method1 excute
method1 end
method2 start
method2 excute
method2 end

可以看出為順序執行

如果在main方法中增加一個

final SynchronizedTest test1=new SynchronizedTest();
並且使用test1調用method2,則輸出結果也是一致的,為什麼呢?

對靜態方法的同步本質上是對類的同步(靜態方法本質上是屬於類的方法,而不是對象上的方法),所以即使test和test2屬於不同的對象,但是它們都屬於SynchronizedTest類的執行個體,所以也只能順序的執行method1和method2,不能並發執行。

 

使用synchronized修飾代碼塊
public class SynchronizedTest {    public void method1(){        System.out.println("method1 start");        try{            synchronized(this){                System.out.println("method1 excute");                Thread.sleep(3000);            }        }catch (Exception e){            e.printStackTrace();        }        System.out.println("method1 end");    }    public void method2(){        System.out.println("method2 start");        try{            synchronized(this){                System.out.println("method2 excute");                Thread.sleep(1000);            }        }catch (Exception e){            e.printStackTrace();        }        System.out.println("method2 end");    }    public static void main(String[] args){        final SynchronizedTest test=new SynchronizedTest();//        final SynchronizedTest test1=new SynchronizedTest();        new Thread(new Runnable() {            @Override            public void run() {                test.method1();            }        }).start();        new Thread(new Runnable() {            @Override            public void run() {                test.method2();            }        }).start();    }}

輸出結果為:

method1 start
method1 excute
method2 start
method1 end
method2 excute
method2 end

可以看出結果為非順序執行,為什嗎?

 

這就涉及到synchronized關鍵字的原理了

先看synchronized代碼塊,首先對代碼進行javac編程,再進行javap -c 反編譯得出結果如下:

 

關於這兩條指令的作用,我們直接參考JVM規範中描述:

monitorenter :

Each object is associated with a monitor. A monitor is locked if and only if it has an owner. The thread that executes monitorenter attempts to gain ownership of the monitor associated with objectref, as follows:
? If the entry count of the monitor associated with objectref is zero, the thread enters the monitor and sets its entry count to one. The thread is then the owner of the monitor.
? If the thread already owns the monitor associated with objectref, it reenters the monitor, incrementing its entry count.
? If another thread already owns the monitor associated with objectref, the thread blocks until the monitor‘s entry count is zero, then tries again to gain ownership.

這段話的大概意思為:

每個對象有一個監視器鎖(monitor)。當monitor被佔用時就會處於鎖定狀態,線程執行monitorenter指令時嘗試擷取monitor的所有權,過程如下:

1、如果monitor的進入數為0,則該線程進入monitor,然後將進入數設定為1,該線程即為monitor的所有者。

2、如果線程已經佔有該monitor,只是重新進入,則進入monitor的進入數加1.

3.如果其他線程已經佔用了monitor,則該線程進入阻塞狀態,直到monitor的進入數為0,再重新嘗試擷取monitor的所有權。

monitorexit: 

The thread that executes monitorexit must be the owner of the monitor associated with the instance referenced by objectref.
The thread decrements the entry count of the monitor associated with objectref. If as a result the value of the entry count is zero, the thread exits the monitor and is no longer its owner. Other threads that are blocking to enter the monitor are allowed to attempt to do so.

這段話的大概意思為:

執行monitorexit的線程必須是objectref所對應的monitor的所有者。

指令執行時,monitor的進入數減1,如果減1後進入數為0,那線程退出monitor,不再是這個monitor的所有者。其他被這個monitor阻塞的線程可以嘗試去擷取這個 monitor 的所有權。 

  通過這兩段描述,我們應該能很清楚的看出Synchronized的實現原理,Synchronized的語義底層是通過一個monitor的對象來完成,其實wait/notify等方法也依賴於monitor對象,這就是為什麼只有在同步的塊或者方法中才能調用wait/notify等方法,否則會拋出java.lang.IllegalMonitorStateException的異常的原因。

 

 

再看下synchronized同步方法:

synchronized 方法控制對類成員變數的訪問:每個類執行個體對應一把鎖,每個 synchronized 方法都必須獲得調用該方法的類執行個體的鎖方能

執行,否則所屬線程阻塞,方法一旦執行,就獨佔該鎖,直到從該方法返回時才將鎖釋放,此後被阻塞的線程方能獲得該鎖,重新進入可執行

狀態。這種機制確保了同一時刻對於每一個類執行個體,其所有聲明為 synchronized 的成員函數中至多隻有一個處於可執行狀態(因為至多隻有

一個能夠獲得該類執行個體對應的鎖),從而有效避免了類成員變數的存取違規(只要所有可能訪問類成員變數的方法均被聲明為 synchronized)

。  
在 Java 中,不光是類執行個體,每一個類也對應一把鎖,這樣我們也可將類的靜態成員函式宣告為 synchronized ,以控制其對類的靜態成

員變數的訪問。

synchronized方法實際上等同於用一個synchronized塊包住方法中的所有語句,然後在synchronized塊的括弧中傳入this關鍵字。當然如果是靜態方法,需要鎖定的則是class對象。

可能一個方法中只有幾行代碼涉及到線程同步的問題,所以synchronized塊比synchronized方法更近細粒度的控制了多個線程的訪問,只有synchronized塊中的內容不能同時被多個線程訪問,方法中的其他語句仍然可以同時被多個線程所訪問(包括synchronized塊之前和之後的)。 

 

運行結果解釋

  有了對Synchronized原理的認識,再來看上面的程式就可以迎刃而解了。

1、同步普通方法結果:

  雖然method1和method2是不同的方法,但是這兩個方法都進行了同步,並且是通過同一個對象去調用的,所以調用之前都需要先去競爭同一個對象上的鎖(monitor),也就只能互斥的擷取到鎖,因此,method1和method2隻能順序的執行。

2、同步靜態方法結果:

  雖然test和test2屬於不同對象,但是test和test2屬於同一個類的不同執行個體,由於method1和method2都屬於靜態同步方法,所以調用的時候需要擷取同一個類上monitor(每個類只對應一個class對象),所以也只能順序的執行。

3、代碼塊同步結果:

  對於代碼塊的同步實質上需要擷取Synchronized關鍵字後面括弧中對象的monitor,由於這段代碼中括弧的內容都是this,而method1和method2又是通過同一的對象去調用的,所以進入同步塊之前需要去競爭同一個對象上的鎖,因此只能順序執行同步塊。

 

Java Synchronized及實現原理

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.