Java多線程筆記二(synchronized的使用以及實現原理)__Java

來源:互聯網
上載者:User
同步關鍵字synchronized

java關鍵字synchronized用來標記方法或者代碼塊是同步的。它是Java中解決並發問題的一種最常用的方法,作用主要有:(1)確保線程互斥的訪問同步代碼(2)保證共用變數的修改能夠及時可見。
synchronized總共有四種用法:
(1)修飾普通方法
(2)修飾靜態方法
(3)修飾普通方法中的代碼塊
(4)修飾靜態方法中的代碼塊 修飾普通方法

public class SynchronizedTest {    //方法1,執行3秒    public synchronized void method1() {        System.out.println("method1 start");        try {            System.out.println("method1 execute");            Thread.sleep(3000);        } catch (InterruptedException e) {            // TODO Auto-generated catch block            e.printStackTrace();        }        System.out.println("method1 end");    }    //方法2,執行1秒    public synchronized void method2() {        System.out.println("method2 start");        try {            System.out.println("method2 execute");            Thread.sleep(1000);        } catch (InterruptedException e) {            // TODO Auto-generated catch block            e.printStackTrace();        }        System.out.println("method2 end");    }    public static void main(String[] args) {        final SynchronizedTest test = new SynchronizedTest();        //線程1        new Thread(new Runnable() {            @Override            public void run() {                test.method1();            }        }).start();        //線程2        new Thread(new Runnable() {            @Override            public void run() {                test.method2();            }        }).start();    }}

輸出:

method1 startmethod1 executemethod1 endmethod2 startmethod2 executemethod2 end

synchronized 關鍵字告訴Java該方法是同步的。同步,是同步在擁有該方法的對象上。對於一個執行個體來說,只有一個線程能夠在這個方法同步塊中運行。如果有多個執行個體存在,每一個執行個體對應一個線程。
在本例中,線程2需要等待線程1的method1執行完成才能開始執行method2方法。因為對於test執行個體來說,同一時間只有一個線程能夠運行它的同步方法。

如果把本例中的synchronized 關鍵字去掉,輸出為:

method1 startmethod1 executemethod2 startmethod2 executemethod2 endmethod1 end

因為線程1和線程2同時進入執行狀態,方法2執行速度比方法1快,所以方法2先執行完成,這個過程中線程1和線程2是同時執行的。 修飾靜態方法

public class SynchronizedTest {    //方法1,執行3秒    public static synchronized void method1() {        System.out.println("method1 start");        try {            System.out.println("method1 execute");            Thread.sleep(3000);        } catch (InterruptedException e) {            // TODO Auto-generated catch block            e.printStackTrace();        }        System.out.println("method1 end");    }    //方法2,執行1秒    public static synchronized void method2() {        System.out.println("method2 start");        try {            System.out.println("method2 execute");            Thread.sleep(1000);        } catch (InterruptedException e) {            // TODO Auto-generated catch block            e.printStackTrace();        }        System.out.println("method2 end");    }    public static void main(String[] args) {        final SynchronizedTest test1 = new SynchronizedTest();        final SynchronizedTest test2 = new SynchronizedTest();        //線程1        new Thread(new Runnable() {            @Override            public void run() {                test1.method1();            }        }).start();        //線程2        new Thread(new Runnable() {            @Override            public void run() {                test2.method2();            }        }).start();    }}

輸出:

method1 startmethod1 executemethod1 endmethod2 startmethod2 executemethod2 end

對靜態方法的同步本質上是對類的同步(靜態方法本質上是屬於類的方法,而不是對象上的方法),所以即使test1和test2屬於不同的對象,但是它們都屬於SynchronizedTest類的執行個體,所以也只能挨個順序執行線程,不能並發執行。 修飾普通方法中的代碼塊

有時你不需要同步整個方法,而是同步方法中的一部分。

public class SynchronizedTest {    //方法1,執行3秒    public void method1() {        System.out.println("method1 start");        try {            synchronized(this){                System.out.println("method1 execute");                Thread.sleep(3000);            }        } catch (InterruptedException e) {            // TODO Auto-generated catch block            e.printStackTrace();        }        System.out.println("method1 end");    }    //方法2,執行1秒    public void method2() {        System.out.println("method2 start");        try {            synchronized(this){                System.out.println("method2 execute");                Thread.sleep(1000);            }        } catch (InterruptedException e) {            // TODO Auto-generated catch block            e.printStackTrace();        }        System.out.println("method2 end");    }    public static void main(String[] args) {        final SynchronizedTest test = new SynchronizedTest();        //線程1        new Thread(new Runnable() {            @Override            public void run() {                test.method1();            }        }).start();        //線程2        new Thread(new Runnable() {            @Override            public void run() {                test.method2();            }        }).start();    }}

輸出:

method1 startmethod1 executemethod2 startmethod1 endmethod2 executemethod2 end

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

在同步構造器中用括弧括起來的對象叫做監視器對象。在上例中,使用了this,即為調用該方法的執行個體本身。上述代碼使用監視器對象同步,並使用調用方法本身的執行個體作為監視器對象。同一時間只有一個線程能夠運行同一個監視器對象的同步塊代碼。 修飾靜態方法中的代碼塊

這和“修飾普通方法中的代碼塊”非常類似,只需要把監視器對象改為類對象即可。以上邊的程式為例,把synchronized(this)改為synchronized(SynchronizedTest.class)即可。 synchronized原理 同步塊程式的反編譯

我們先通過反編譯下面的代碼來看看Synchronized是如何?對同步塊進行同步的:

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

反編譯結果:

紅色框中的這兩條指令(monitorenter和monitorexit)的作用是什麼。先看monitorenter :

每個對象有一個監視器鎖(monitor)。當monitor被佔用時就會處於鎖定狀態,線程執行monitorenter指令時嘗試擷取monitor的所有權,過程如下:
(1)如果monitor的進入數為0,則該線程進入monitor,然後將進入數設定為1,該線程即為monitor的所有者。
(2)如果線程已經佔有該monitor,只是重新進入,則進入monitor的進入數加1。
(3)如果其他線程已經佔用了monitor,則該線程進入阻塞狀態,直到monitor的進入數為0,再重新嘗試擷取monitor的所有權。

再看monitorexit: 

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

通過上邊兩段描述,我們可以看出在同步代碼塊中synchronized的實現原理,是通過一個monitor的對象來完成,其實wait/notify等方法也依賴於monitor對象,這就是為什麼只有在同步的塊或者方法中才能調用wait/notify等方法,否則會拋出java.lang.IllegalMonitorStateException異常的原因。 同步方法的反編譯:


從反編譯的結果來看,方法的同步並沒有通過指令monitorenter和monitorexit來完成(理論上其實也可以通過這兩條指令來實現),不過其常量池中多了ACC_SYNCHRONIZED標示符。JVM就是根據該標示符來實現方法的同步的,具體的:
當方法調用時,調用指令將會檢查方法的 ACC_SYNCHRONIZED 訪問標誌是否被設定,如果設定了,執行線程將先擷取monitor,擷取成功之後才能執行方法體,方法執行完後再釋放monitor。在方法執行期間,其他任何線程都無法再獲得同一個monitor對象。 其實本質上沒有區別,只是方法的同步是一種隱式的方式來實現,無需通過位元組碼來完成。 回顧

有了對Synchronized原理的認識,再來看上面的程式就可以迎刃而解了。
(1)“修飾普通方法”一節中的例子:
雖然method1和method2是不同的方法,但是這兩個方法都進行了同步,並且是通過同一個對象去調用的,所以調用之前都需要先去競爭同一個對象上的鎖(monitor),也就只能互斥的擷取到鎖,因此,method1和method2隻能順序的執行。

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

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


原文來自:並發編程網-Java同步塊
liuxiaopeng-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.