中文字幕在线观看,亚洲а∨天堂久久精品9966,亚洲成a人片在线观看你懂的,亚洲av成人片无码网站,亚洲国产精品无码久久久五月天

JAVA 同步實現(xiàn)原理

2018-07-02    來源:importnew

容器云強勢上線!快速搭建集群,上萬Linux鏡像隨意使用

Synchronized的基本使用

Synchronized是Java中解決并發(fā)問題的一種最常用的方法,也是最簡單的一種方法。Synchronized的作用主要有三個:

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

從語法上講,Synchronized總共有三種用法:

  1. 修飾普通方法
  2. 修飾靜態(tài)方法
  3. 修飾代碼塊

接下來我就通過幾個例子程序來說明一下這三種使用方式(為了便于比較,三段代碼除了Synchronized的使用方式不同以外,其他基本保持一致)。

  • 沒有同步的情況
package com.paddx.test.concurrent;

public class SynchronizedTest {
    public void method1(){
        System.out.println("Method 1 start");
        try {
            System.out.println("Method 1 execute");
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Method 1 end");
    }

    public void method2(){
        System.out.println("Method 2 start");
        try {
            System.out.println("Method 2 execute");
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Method 2 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();
    }
}

執(zhí)行結果如下,線程1和線程2同時進入執(zhí)行狀態(tài),線程2執(zhí)行速度比線程1快,所以線程2先執(zhí)行完成,這個過程中線程1和線程2是同時執(zhí)行的。

Method 1 start
Method 1 execute
Method 2 start
Method 2 execute
Method 2 end
Method 1 end
  • 對普通方法同步
package com.paddx.test.concurrent;

public class SynchronizedTest {
    public synchronized void method1(){
        System.out.println("Method 1 start");
        try {
            System.out.println("Method 1 execute");
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Method 1 end");
    }

    public synchronized void method2(){
        System.out.println("Method 2 start");
        try {
            System.out.println("Method 2 execute");
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Method 2 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();
    }
}

執(zhí)行結果如下,跟代碼段一比較,可以很明顯的看出,線程2需要等待線程1的method1執(zhí)行完成才能開始執(zhí)行method2方法。

Method 1 start
Method 1 execute
Method 1 end
Method 2 start
Method 2 execute
Method 2 end
  • 靜態(tài)方法(類)同步
package com.paddx.test.concurrent;

 public class SynchronizedTest {
     public static synchronized void method1(){
         System.out.println("Method 1 start");
         try {
             System.out.println("Method 1 execute");
             Thread.sleep(3000);
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
         System.out.println("Method 1 end");
     }

     public static synchronized void method2(){
         System.out.println("Method 2 start");
         try {
             System.out.println("Method 2 execute");
             Thread.sleep(1000);
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
         System.out.println("Method 2 end");
     }

     public static void main(String[] args) {
         final SynchronizedTest test = new SynchronizedTest();
         final SynchronizedTest test2 = new SynchronizedTest();

         new Thread(new Runnable() {
             @Override
             public void run() {
                 test.method1();
             }
         }).start();

         new Thread(new Runnable() {
             @Override
             public void run() {
                 test2.method2();
             }
         }).start();
     }
 }

執(zhí)行結果如下,對靜態(tài)方法的同步本質(zhì)上是對類的同步(靜態(tài)方法本質(zhì)上是屬于類的方法,而不是對象上的方法),所以即使test和test2屬于不同的對象,但是它們都屬于SynchronizedTest類的實例,所以也只能順序的執(zhí)行method1和method2,不能并發(fā)執(zhí)行。

Method 1 start
Method 1 execute
Method 1 end
Method 2 start
Method 2 execute
Method 2 end
  • 代碼塊同步
package com.paddx.test.concurrent;

public class SynchronizedTest {
    public void method1(){
        System.out.println("Method 1 start");
        try {
            synchronized (this) {
                System.out.println("Method 1 execute");
                Thread.sleep(3000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Method 1 end");
    }

    public void method2(){
        System.out.println("Method 2 start");
        try {
            synchronized (this) {
                System.out.println("Method 2 execute");
                Thread.sleep(1000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Method 2 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();
    }
}

執(zhí)行結果如下,雖然線程1和線程2都進入了對應的方法開始執(zhí)行,但是線程2在進入同步塊之前,需要等待線程1中同步塊執(zhí)行完成。

Method 1 start
Method 1 execute
Method 2 start
Method 1 end
Method 2 execute
Method 2 end

Synchronized 原理

先通過反編譯下面的代碼來看看Synchronized是如何實現(xiàn)對代碼塊進行同步的

package com.paddx.test.concurrent;

public class SynchronizedDemo {
    public void method() {
        synchronized (this) {
            System.out.println("Method 1 start");
        }
    }
}

反編譯結果

關于這兩條指令的作用,我們直接參考JVM規(guī)范中描述: ` 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. `

每個對象有一個監(jiān)視器鎖(monitor)。當monitor被占用時就會處于鎖定狀態(tài),線程執(zhí)行monitorenter指令時嘗試獲取monitor的所有權,過程如下:

  1. 如果monitor的進入數(shù)為0,則該線程進入monitor,然后將進入數(shù)設置為1,該線程即為monitor的所有者。
  2. 如果線程已經(jīng)占有該monitor,只是重新進入,則進入monitor的進入數(shù)加1.
  3. 如果其他線程已經(jīng)占用了monitor,則該線程進入阻塞狀態(tài),直到monitor的進入數(shù)為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.`

  1. 執(zhí)行monitorexit的線程必須是objectref所對應的monitor的所有者。
  2. 指令執(zhí)行時,monitor的進入數(shù)減1,如果減1后進入數(shù)為0,那線程退出monitor,不再是這個monitor的所有者。其他被這個monitor阻塞的線程可以嘗試去獲取這個 monitor 的所有權。

通過這兩段描述,我們應該能很清楚的看出?Synchronized的實現(xiàn)原理

Synchronized的語義底層是通過一個monitor的對象來完成,其實wait/notify等方法也依賴于monitor對象,這就是為什么只有在同步的塊或者方法中才能調(diào)用wait/notify等方法,否則會拋出java.lang.IllegalMonitorStateException的異常的原因。

我們再來看一下同步方法的反編譯結果:

package com.paddx.test.concurrent;

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

反編譯結果

從反編譯的結果來看,方法的同步并沒有通過指令monitorenter和monitorexit來完成(理論上其實也可以通過這兩條指令來實現(xiàn)),不過相對于普通方法,其常量池中多了ACC_SYNCHRONIZED標示符。JVM就是根據(jù)該標示符來實現(xiàn)方法的同步的:當方法調(diào)用時,調(diào)用指令將會檢查方法的 ACC_SYNCHRONIZED 訪問標志是否被設置,如果設置了,執(zhí)行線程將先獲取monitor,獲取成功之后才能執(zhí)行方法體,方法執(zhí)行完后再釋放monitor。在方法執(zhí)行期間,其他任何線程都無法再獲得同一個monitor對象。 其實本質(zhì)上沒有區(qū)別,只是方法的同步是一種隱式的方式來實現(xiàn),無需通過字節(jié)碼來完成。

運行結果解釋

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

  1. 代碼段2結果: 雖然method1和method2是不同的方法,但是這兩個方法都進行了同步,并且是通過同一個對象去調(diào)用的,所以調(diào)用之前都需要先去競爭同一個對象上的鎖(monitor),也就只能互斥的獲取到鎖,因此,method1和method2只能順序的執(zhí)行。
  2. 代碼段3結果: 雖然test和test2屬于不同對象,但是test和test2屬于同一個類的不同實例,由于method1和method2都屬于靜態(tài)同步方法,所以調(diào)用的時候需要獲取同一個類上monitor(每個類只對應一個class對象),所以也只能順序的執(zhí)行。
  3. 代碼段4結果: 對于代碼塊的同步實質(zhì)上需要獲取Synchronized關鍵字后面括號中對象的monitor,由于這段代碼中括號的內(nèi)容都是this,而method1和method2又是通過同一的對象去調(diào)用的,所以進入同步塊之前需要去競爭同一個對象上的鎖,因此只能順序執(zhí)行同步塊。

總結

Synchronized是通過對象內(nèi)部的一個叫做監(jiān)視器鎖(monitor)來實現(xiàn)的。但是監(jiān)視器鎖本質(zhì)又是依賴于底層的操作系統(tǒng)的Mutex Lock來實現(xiàn)的。而操作系統(tǒng)實現(xiàn)線程之間的切換這就需要從用戶態(tài)轉(zhuǎn)換到內(nèi)核態(tài),這個成本非常高,狀態(tài)之間的轉(zhuǎn)換需要相對比較長的時間,這就是為什么Synchronized效率低的原因。

因此,這種依賴于操作系統(tǒng)Mutex Lock所實現(xiàn)的鎖我們稱之為“重量級鎖”。JDK中對Synchronized做的種種優(yōu)化,其核心都是為了減少這種重量級鎖的使用。JDK1.6以后,為了減少獲得鎖和釋放鎖所帶來的性能消耗,提高性能,引入了“偏向鎖”和“輕量級鎖”。無鎖 --> 偏向鎖 --> 輕量級 --> 重量級

標簽: 代碼

版權申明:本站文章部分自網(wǎng)絡,如有侵權,請聯(lián)系:west999com@outlook.com
特別注意:本站所有轉(zhuǎn)載文章言論不代表本站觀點!
本站所提供的圖片等素材,版權歸原作者所有,如需使用,請與原作者聯(lián)系。

上一篇:Kafka 源碼分析2 : Network相關

下一篇:Java:關于值傳遞你需要了解的事情