Java併發編程:Synchronized及其實現原理

 
1、Synchronized的基本使用

    Synchronized是Java中解決併發問題的一種最經常使用的方法,也是最簡單的一種方式。結合上篇《Java併發編程:核心理論》的論述Synchronized的主要做用分爲如下三個:java

  1. 保證線程互斥的訪問同步代碼
  2. 保證共享變量的修改的可見性
  3. 有效的解決重排序

從語法的角度講,Synchronized又三種如下用法:編程

  1. 修飾普通方法
  2. 修飾靜態方法
  3. 修飾代碼塊

下面同個幾個代碼段來看看這三種方式在有Synchronized和沒有下的狀況:安全

一、普通方法沒有同步的狀況:併發

public class studentSychronized {

    public void method_1(){
        System.out.println("方法1啓動。。。");
        try {
            Thread.sleep(3000);
            System.out.println("方法1執行中。。。");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("方法1結束。。。");
    }
    public void method_2(){
        System.out.println("方法2啓動。。。");
        try {
            Thread.sleep(3000);
            System.out.println("方法2執行中。。。");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("方法2結束。。。");
    }

    public static void main(String[] args) {
        studentSychronized student = new studentSychronized();

        new Thread(new Runnable() {
            @Override
            public void run() {
                student.method_1();
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                student.method_2();
            }
        }).start();

    }
}

執行的結果以下,兩個線程執行的方法同時開始啓動,線程1執行的比線程2快,因此先結束。多執行幾回會發現輸出的結果有可能線程1比線程2快,也有可能線程2比線程1快。能夠發現兩個線程是交替執行的。ide

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

二、對方法加Sychronized修飾,同步執行this

public class studentSychronized {

    public synchronized void method_1(){
        System.out.println("方法1啓動。。。");
        try {
            Thread.sleep(3000);
            System.out.println("方法1執行中。。。");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("方法1結束。。。");
    }
    public synchronized void method_2(){
        System.out.println("方法2啓動。。。");
        try {
            Thread.sleep(3000);
            System.out.println("方法2執行中。。。");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("方法2結束。。。");
    }

    public static void main(String[] args) {
        studentSychronized student = new studentSychronized();

        new Thread(new Runnable() {
            @Override
            public void run() {
                student.method_1();
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                student.method_2();
            }
        }).start();

    }
}

執行的結果以下,能夠看到線程2是等待線程1執行完成後纔開始執行。爲何加上sychronized會出現這種結果,咱們先按下不表,接着往下看。線程

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

三、靜態方法(類)同步code

public class studentSychronized {

    public static synchronized void method_1(){
        System.out.println("方法1啓動。。。");
        try {
            Thread.sleep(3000);
            System.out.println("方法1執行中。。。");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("方法1結束。。。");
    }
    public static synchronized void method_2(){
        System.out.println("方法2啓動。。。");
        try {
            Thread.sleep(3000);
            System.out.println("方法2執行中。。。");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("方法2結束。。。");
    }

    public static void main(String[] args) {
        studentSychronized student = new studentSychronized();
        studentSychronized student1 = new studentSychronized();

        new Thread(new Runnable() {
            @Override
            public void run() {
                student.method_1();
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                student1.method_2();
            }
        }).start();

    }
}

    執行結果以下,對靜態方法的同步本質上是對類的同步(由於static關鍵字的特效,靜態方法本質是上屬於類的方法,而不是對象的方法),因此即便同一個類的不一樣的實例也只能順序執行,不能併發執行。對象

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

四、代碼塊同步blog

public class studentSychronized {

    public  void method_1(){


        try {
            synchronized (this){
                System.out.println("方法1啓動。。。");
                System.out.println("方法1執行中。。。");
                Thread.sleep(3000);
            }

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("方法1結束。。。");
    }
    public  void method_2(){
        System.out.println("方法2啓動。。。");
        try {
            synchronized (this){
                Thread.sleep(3000);
                System.out.println("方法2執行中。。。");
            }


        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("方法2結束。。。");
    }

    public static void main(String[] args) {
         studentSychronized student = new studentSychronized();
         studentSychronized student1 = new studentSychronized();

        new Thread(new Runnable() {
            @Override
            public void run() {
                student.method_1();
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                student1.method_2();
            }
        }).start();

    }
}

執行結果以下,雖然兩個線程是同時開始執行,可是在線程2進入同步代碼塊的時候先是等待線程1的代碼塊執行完後繼續執行。

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

2、Synchronized工做原理

    帶着上述代碼執行的結果的疑問,咱們先來了解下Synchronized的原理。等了解完原理,我相信這些疑問會迎刃而解。首先經過反編譯下面的代碼來看看Synchronized是如何進行同步的:

代碼:

public class sychronizedDemo {
    public void method(){
        synchronized (this){
            System.out.println("HELLO !!!");
        }
    }
}

反編譯結果:

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

在反編譯結果中咱們發現有兩個指令:monitorenter/monitorexit.咱們看看這兩條指令JVM規範中是怎麼描述的。

JVM規範中對monitorenter和monitorexit指令的描述以下:
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的全部權,過程以下:
一、若是monitor的進入數爲0,則該線程進入monitor,而後將進入數設置爲1,該線程即爲monitor的全部者。
二、若是線程已經佔有該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的異常的緣由。

接下來咱們再來看一段代碼:

public class SynchronizedMethod {
     public synchronized void method() {
         System.out.println("Hello World!");
    }
 }

反編譯結果

watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=

    從反編譯的結果咱們能發現,普通方法的同步並無經過指令 monitorenter和monitorexit 來完成,不過同步方法比普通方法在常量池中多了ACC_SYNCHRONIZED標示符。JVM就是根據該標識符來實現方法的同步的:

    當方法被調用時,調用指令會檢查方法的ACC_SYNCHRONIZED訪問標識是否被設置,若是設置了,執行線程將先獲取該方法的monitor對象,獲取成功以後才能執行方法體,方法執行完後再釋放monitor對象。在方法執行期間,其餘線程都是沒法再次獲取同一個minitor對象。其實本質上沒有區別,只是在方法的同步時換了一種方式來實現。

    瞭解了synchronized的工做原理,咱們能夠在回過頭去看看咱們開頭的代碼,執行結果的原理我相信你們都一目瞭然了。

三:總結

      Synchronized是Java併發編程中最經常使用的用於保證線程安全的方式,其使用相對也比較簡單。可是若是可以深刻了解其原理,對監視器鎖等底層知識有所瞭解,一方面能夠幫助咱們正確的使用Synchronized關鍵字,另外一方面也可以幫助咱們更好的理解併發編程機制,有助咱們在不一樣的狀況下選擇更優的併發策略來完成任務。對平時遇到的各類併發問題,也可以從容的應對。

相關文章
相關標籤/搜索