Java Synchronized及實現原理


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

可以看出結果為非順序執行,為什么? 其實是順序的,看后面原理就明白了, 只不過同步關鍵字加在代碼庫里面,method2的start 打印會先輸出,后面同步競爭時還是順序的。

 

這就涉及到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又是通過同一的對象去調用的,所以進入同步塊之前需要去競爭同一個對象上的鎖,因此只能順序執行同步塊。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM