Java synchronized關鍵字詳解

synchronized關鍵字可以保證方法或代碼塊運行時,同一時刻只有一個方法進入臨界區,同時能夠保證共享變量在內存的可見性。bash

1、原理

synchronized能夠實現同步代碼塊、同步方法。數據結構

同步代碼塊:是經過monitorenter和monitorexit指令,配合monitor實現的。異步

monitor:async

monitor能夠理解爲一個同步工具或一種同步機制,一般被描述爲一個對象。每個Java對象就有一把看不見的鎖,稱爲內部鎖或者Monitor鎖。Monitor是線程私有的數據結構,每個線程都有一個可用monitor record列表,同時還有一個全局的可用列表。每個被鎖住的對象都會和一個monitor關聯,同時monitor中有一個Owner字段存放擁有該鎖的線程的惟一標識,表示該鎖被這個線程佔用。ide


monitorenter指令插入到同步代碼塊的開始位置,monitorexit指令插入到同步代碼塊的結束位置,兩條指令一一對應。且任何對象都有一個monitor與之相關聯,當且一個monitor被持有以後,他將處於鎖定狀態。線程執行到monitorenter指令時,將會嘗試獲取對象所對應的monitor全部權,即嘗試獲取對象的鎖; 工具

同步方法:Class文件的方法表中將該方法的access_flags字段中的synchronized標誌位置1。post

2、使用

  1. 對象鎖&類鎖

    Java中的鎖根據鎖的內容分爲:對象鎖、類鎖。測試

    對象鎖:在 Java 中,每一個對象都會有一個 monitor 對象,這個對象其實就是 Java 對象的鎖,一般會被稱爲「內置鎖」或「對象鎖」。類的對象能夠有多個,因此每一個對象有其獨立的對象鎖,互不干擾。this

    類鎖:在 Java 中,針對每一個類也有一個鎖,能夠稱爲「類鎖」,類鎖其實是經過對象鎖實現的,即類的 Class 對象鎖。每一個類只有一個 Class 對象,因此每一個類只有一個類鎖。spa

  2. synchronized用法分類

    可用從兩個維度進行分類:

    根據修飾對象分類

    修飾代碼塊:

    • synchronized(this|object) {}

    • synchronized(類.class) {}

    修飾方法:

    • 修飾非靜態方法

    • 修飾靜態方法

    根據獲取的鎖分類

    獲取對象鎖:

    • synchronized(this|object) {}

    • 修飾非靜態方法

    獲取類鎖:

    • synchronized(類.class) {}

    • 修飾靜態方法

  3. synchronized用法詳解

獲取對象鎖

對於同一對象

/* 同步線程類 */
class SyncThread implements Runnable {
    @Override
    public void run() {
        String threadName = Thread.currentThread().getName();
        if (threadName.startsWith("A")) {
            async();
        } else if (threadName.startsWith("B")) {
            sync1();
        } else if (threadName.startsWith("C")) {
            sync2();
        }
    }
    /**
     * 非同步方法
     */
    private void async() {
        try {
            System.out.println(Thread.currentThread().getName() + "_Async_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
            Thread.sleep(2000);
            System.out.println(Thread.currentThread().getName() + "_Async_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    /**
     * 方法中有 synchronized(this|object) {} 同步代碼塊
     */
    private void sync1() {
        System.out.println(Thread.currentThread().getName() + "_Sync1: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
        synchronized (this) {
            try {
                System.out.println(Thread.currentThread().getName() + "_Sync1_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
                Thread.sleep(2000);
                System.out.println(Thread.currentThread().getName() + "_Sync1_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    /**
     * synchronized 修飾非靜態方法
     */
    private synchronized void sync2() {
        System.out.println(Thread.currentThread().getName() + "_Sync2: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
        try {
            System.out.println(Thread.currentThread().getName() + "_Sync2_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
            Thread.sleep(2000);
            System.out.println(Thread.currentThread().getName() + "_Sync2_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

/* 測試代碼: */
public class SyncTest {
    public static void main(String... args) {
        SyncThread syncThread = new SyncThread();
        Thread A_thread1 = new Thread(syncThread, "A_thread1");
        Thread A_thread2 = new Thread(syncThread, "A_thread2");
        Thread B_thread1 = new Thread(syncThread, "B_thread1");
        Thread B_thread2 = new Thread(syncThread, "B_thread2");
        Thread C_thread1 = new Thread(syncThread, "C_thread1");
        Thread C_thread2 = new Thread(syncThread, "C_thread2");
        A_thread1.start();
        A_thread2.start();
        B_thread1.start();
        B_thread2.start();
        C_thread1.start();
        C_thread2.start();
    }
}
/* 運行結果 */
B_thread2_Sync1: 14:44:20
A_thread1_Async_Start: 14:44:20
B_thread1_Sync1: 14:44:20
C_thread1_Sync2: 14:44:20
A_thread2_Async_Start: 14:44:20
C_thread1_Sync2_Start: 14:44:20
A_thread1_Async_End: 14:44:22
A_thread2_Async_End: 14:44:22
C_thread1_Sync2_End: 14:44:22
B_thread1_Sync1_Start: 14:44:22
B_thread1_Sync1_End: 14:44:24
B_thread2_Sync1_Start: 14:44:24
B_thread2_Sync1_End: 14:44:26
C_thread2_Sync2: 14:44:26
C_thread2_Sync2_Start: 14:44:26
C_thread2_Sync2_End: 14:44:28複製代碼

結果分析:

  1. A類線程非同步,線程運行過程當中另外的線程也可能訪問該對象的非同步代碼塊,也就是打印結果上顯示是start到end中間會被打斷。

  2. B類線程中有同步代碼塊,當一個線程運行過程當中,另外的線程訪問同步代碼塊會被阻塞。注意:synchronized(this|object) {} 代碼塊 {} 以外的代碼依然是非同步的。

  3. C 類線程訪問的是 synchronized 修飾非靜態方法,C 類線程是同步的,一個線程在訪問對象的同步代方法,另外一個訪問對象同步方法的線程會被阻塞。synchronized 修飾非靜態方法,做用範圍是整個方法,因此方法中全部的代碼都是同步的。

  4. 不難發現,B類線程和C類線程都是訪問的同一個對象的對象鎖,因此B和C線程間也是同步的。

對於不一樣對象

/* 測試代碼: */
public class SyncTest {
    public static void main(String... args) {
        Thread A_thread1 = new Thread(new SyncThread(), "A_thread1");
        Thread A_thread2 = new Thread(new SyncThread(), "A_thread2");
        Thread B_thread1 = new Thread(new SyncThread(), "B_thread1");
        Thread B_thread2 = new Thread(new SyncThread(), "B_thread2");
        Thread C_thread1 = new Thread(new SyncThread(), "C_thread1");
        Thread C_thread2 = new Thread(new SyncThread(), "C_thread2");
        A_thread1.start();
        A_thread2.start();
        B_thread1.start();
        B_thread2.start();
        C_thread1.start();
        C_thread2.start();
    }
}
/* 運行結果 */
A_thread2_Async_Start: 15:01:34
C_thread2_Sync2: 15:01:34
B_thread2_Sync1: 15:01:34
C_thread1_Sync2: 15:01:34
B_thread2_Sync1_Start: 15:01:34
B_thread1_Sync1: 15:01:34
C_thread1_Sync2_Start: 15:01:34
A_thread1_Async_Start: 15:01:34
C_thread2_Sync2_Start: 15:01:34
B_thread1_Sync1_Start: 15:01:34
C_thread1_Sync2_End: 15:01:36
A_thread1_Async_End: 15:01:36
C_thread2_Sync2_End: 15:01:36
B_thread2_Sync1_End: 15:01:36
B_thread1_Sync1_End: 15:01:36
A_thread2_Async_End: 15:01:36複製代碼

結果分析:兩個線程訪問不一樣對象的 synchronized(this|object) {} 代碼塊和 synchronized 修飾非靜態方法是異步的,同一個類的不一樣對象的對象鎖互不干擾。

獲取類鎖

對於同一對象

/* 同步線程類 */
class SyncThread implements Runnable {
    @Override
    public void run() {
        String threadName = Thread.currentThread().getName();
        if (threadName.startsWith("A")) {
            async();
        } else if (threadName.startsWith("B")) {
            sync1();
        } else if (threadName.startsWith("C")) {
            sync2();
        }
    }
    /**
     * 異步方法
     */
    private void async() {
        try {
            System.out.println(Thread.currentThread().getName() + "_Async_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
            Thread.sleep(2000);
            System.out.println(Thread.currentThread().getName() + "_Async_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    /**
     * 方法中有 synchronized(類.class) {} 同步代碼塊
     */
    private void sync1() {
        System.out.println(Thread.currentThread().getName() + "_Sync1: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
        synchronized (SyncThread.class) {
            try {
                System.out.println(Thread.currentThread().getName() + "_Sync1_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
                Thread.sleep(2000);
                System.out.println(Thread.currentThread().getName() + "_Sync1_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    /**
     * synchronized 修飾靜態方法
     */
    private synchronized static void sync2() {
        System.out.println(Thread.currentThread().getName() + "_Sync2: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
        try {
            System.out.println(Thread.currentThread().getName() + "_Sync2_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
            Thread.sleep(2000);
            System.out.println(Thread.currentThread().getName() + "_Sync2_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

/* 測試代碼 */
public class SyncTest {
    public static void main(String... args) {
        SyncThread syncThread = new SyncThread();
        Thread A_thread1 = new Thread(syncThread, "A_thread1");
        Thread A_thread2 = new Thread(syncThread, "A_thread2");
        Thread B_thread1 = new Thread(syncThread, "B_thread1");
        Thread B_thread2 = new Thread(syncThread, "B_thread2");
        Thread C_thread1 = new Thread(syncThread, "C_thread1");
        Thread C_thread2 = new Thread(syncThread, "C_thread2");
        A_thread1.start();
        A_thread2.start();
        B_thread1.start();
        B_thread2.start();
        C_thread1.start();
        C_thread2.start();
    }
}
/* 運行結果 */
B_thread1_Sync1: 15:08:13
C_thread1_Sync2: 15:08:13
B_thread2_Sync1: 15:08:13
A_thread1_Async_Start: 15:08:13
C_thread1_Sync2_Start: 15:08:13
A_thread2_Async_Start: 15:08:13
C_thread1_Sync2_End: 15:08:15
A_thread2_Async_End: 15:08:15
A_thread1_Async_End: 15:08:15
B_thread2_Sync1_Start: 15:08:15
B_thread2_Sync1_End: 15:08:17
B_thread1_Sync1_Start: 15:08:17
B_thread1_Sync1_End: 15:08:19
C_thread2_Sync2: 15:08:19
C_thread2_Sync2_Start: 15:08:19
C_thread2_Sync2_End: 15:08:21複製代碼

結果分析:同一對象的狀況下,類鎖和對象鎖的行爲一致。

對於不一樣對象

/* 測試代碼 */
public class SyncTest {
    public static void main(String... args) {
        Thread A_thread1 = new Thread(new SyncThread(), "A_thread1");
        Thread A_thread2 = new Thread(new SyncThread(), "A_thread2");
        Thread B_thread1 = new Thread(new SyncThread(), "B_thread1");
        Thread B_thread2 = new Thread(new SyncThread(), "B_thread2");
        Thread C_thread1 = new Thread(new SyncThread(), "C_thread1");
        Thread C_thread2 = new Thread(new SyncThread(), "C_thread2");
        A_thread1.start();
        A_thread2.start();
        B_thread1.start();
        B_thread2.start();
        C_thread1.start();
        C_thread2.start();
    }
}
/* 運行結果 */
A_thread2_Async_Start: 15:17:28
B_thread2_Sync1: 15:17:28
A_thread1_Async_Start: 15:17:28
B_thread1_Sync1: 15:17:28
C_thread1_Sync2: 15:17:28
C_thread1_Sync2_Start: 15:17:28
C_thread1_Sync2_End: 15:17:30
A_thread2_Async_End: 15:17:30
B_thread1_Sync1_Start: 15:17:30
A_thread1_Async_End: 15:17:30
B_thread1_Sync1_End: 15:17:32
B_thread2_Sync1_Start: 15:17:32
B_thread2_Sync1_End: 15:17:34
C_thread2_Sync2: 15:17:34
C_thread2_Sync2_Start: 15:17:34
C_thread2_Sync2_End: 15:17:36複製代碼

結果分析:同一個類的不一樣對象的類鎖是同一個,也就是多個線程訪問同一個類的類鎖中的代碼仍是同步的。

類中同時存在類鎖和對象鎖

結果:對象鎖和類鎖是獨立的,互不干擾,非同步。

3、補充

  1. synchronized關鍵字不能繼承。對於父類中的 synchronized 修飾方法,子類在覆蓋該方法時,默認狀況下不是同步的,必須顯示的使用 synchronized 關鍵字修飾才行。

  2. 定義接口方法時不能使用synchronized關鍵字。

  3. 構造方法不能使用synchronized關鍵字,但可使用synchronized代碼塊來進行同步。

4、參考

blog.csdn.net/chenssy/art…

juejin.im/post/594a24…

相關文章
相關標籤/搜索