新手向-同步關鍵字synchronized對this、class、object、方法的區別


在看源代碼時遇到多線程需要同步的時候,總是會看見幾種寫法,修飾方法、修飾靜態方法、synchronized(Xxx.class)synchronized(this)synchronized(obj),之前一直沒深究幾種方式的區別,現在想來真是驚出一身冷汗,居然這個問題都沒有仔細想清楚。

synchronized的語義

每個對象都有一個監視器monitor,被synchronized修飾,語義就是獲取這個對象的monitor,反編譯后可以看到monitorenter和monitorexit。synchronized關鍵字有三種應用方式(其實按標題來講應該是5種,但是其中有兩種都是與另外兩種等價的):

  • 修飾實例方法
  • 修飾靜態方法
  • 修飾代碼塊(指定對象)

實驗

先上代碼做實驗驗證一下

public class SyncThread {
    private final Object lock = new Object();

    public void foo() throws Exception {
        synchronized (SyncThread.class) {
            for (int i = 0; i < 5; i++) {
                System.out.println(">>>foo: " + i);
                Thread.sleep(1000);
            }
        }
    }

    public void bar() throws Exception {
        synchronized (this) {
            for (int i = 0; i < 5; i++) {
                System.out.println("<<<bar: " + i);
                Thread.sleep(1000);
            }
        }
    }

    public void cpp() throws Exception {
        synchronized (lock) {
            for (int i = 0; i < 5; i++) {
                System.out.println("===cpp: " + i);
                Thread.sleep(1000);
            }
        }
    }

    public void der() throws Exception {
        for (int i = 0; i < 5; i++) {
            System.out.println("!!!der: " + i);
            Thread.sleep(1000);
        }
    }
}

可以看到有四種不同的synchronized修飾,以及一個沒有同步的方法,再運行一下看看結果

public class ThreadApp {

    public static void main(String[] args) {
        final SyncThread syncThread = new SyncThread();
        new Thread(() -> {
            try {
                syncThread.foo();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }).start();
        new Thread(() -> {
            try {
                syncThread.bar();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }).start();
        new Thread(() -> {
            try {
                syncThread.cpp();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }).start();
        new Thread(() -> {
            try {
                syncThread.der();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }).start();
    }
}

可以看到結果

>>>foo: 0
<<<bar: 0
===cpp: 0
!!!der: 0
>>>foo: 1
===cpp: 1
<<<bar: 1
!!!der: 1
>>>foo: 2
!!!der: 2
<<<bar: 2
===cpp: 2
>>>foo: 3
<<<bar: 3
===cpp: 3
!!!der: 3
>>>foo: 4
!!!der: 4
<<<bar: 4
===cpp: 4

分析

從以上結果來看各線程並沒有發生競爭,互不影響,其實明白了synchronized語義也很好理解以上結果,幾個synchronized獲取的monitor都不是一個,當然相互不影響。
但是值得注意的幾點:

  • synchronized(Xxx.class)獲取的是類的monitor,所以與public synchronized static void some()修飾靜態方法是等價的
  • synchronized(this)獲取的是當前實例的monitor,所以與public synchronized void some()修飾實例方法是等價的

以上兩點可以通過修改上述代碼中方法可以很容易驗證,我們修改最后一個方法

public synchronized static void der() throws Exception {
    for (int i = 0; i < 5; i++) {
        System.out.println("!!!der: " + i);
        Thread.sleep(1000);
    }
}

運行得到結果

!!!der: 0
!!!der: 1
!!!der: 2
!!!der: 3
!!!der: 4
>>>foo: 0
>>>foo: 1
>>>foo: 2
>>>foo: 3
>>>foo: 4

可以看到,確實synchronized(Xxx.class)synchronized修飾靜態方法是等價的。再修改為synchronized修飾實例方法

public synchronized void der() throws Exception {
    for (int i = 0; i < 5; i++) {
        System.out.println("!!!der: " + i);
        Thread.sleep(1000);
    }
}

運行查看結果

<<<bar: 0
<<<bar: 1
<<<bar: 2
<<<bar: 3
<<<bar: 4
!!!der: 0
!!!der: 1
!!!der: 2
!!!der: 3
!!!der: 4

可以看到,確實synchronized(this)synchronized修飾實例方法是等價的。
總之,個人認為要理解幾種不一樣的地方,關鍵是理解清楚是獲取的誰的monitor,只要是同一個monitor,當然就會發生同步!


免責聲明!

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



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