Java 之 synchronized 詳解

1、概念

synchronized 是 Java 中的關鍵字,是利用鎖的機制來實現同步的。javascript

鎖機制有以下兩種特性:java

  • 互斥性:即在同一時間只容許一個線程持有某個對象鎖,經過這種特性來實現多線程中的協調機制,這樣在同一時間只有一個線程對需同步的代碼塊(複合操做)進行訪問。互斥性咱們也每每稱爲操做的原子性。緩存

  • 可見性:必須確保在鎖被釋放以前,對共享變量所作的修改,對於隨後得到該鎖的另外一個線程是可見的(即在得到鎖時應得到最新共享變量的值),不然另外一個線程多是在本地緩存的某個副本上繼續操做從而引發不一致。多線程

2、對象鎖和類鎖

1. 對象鎖

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

2. 類鎖

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

3、synchronized 的用法分類

synchronized 的用法能夠從兩個維度上面分類:ide

1. 根據修飾對象分類

synchronized 能夠修飾方法和代碼塊測試

  • 修飾代碼塊ui

    • synchronized(this|object) {}this

    • synchronized(類.class) {}

  • 修飾方法

    • 修飾非靜態方法

    • 修飾靜態方法

2. 根據獲取的鎖分類

  • 獲取對象鎖

    • synchronized(this|object) {}

    • 修飾非靜態方法

  • 獲取類鎖

    • synchronized(類.class) {}

    • 修飾靜態方法

4、synchronized 的用法詳解

這裏根據獲取的鎖分類來分析 synchronized 的用法

1. 獲取對象鎖

1.1 對於同一對象

  • 同步線程類:
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複製代碼
  • 結果分析:

    • A 類線程訪問方法中沒有同步代碼塊,A 類線程是異步的,因此有線程訪問對象的同步代碼塊時,另外的線程能夠訪問該對象的非同步代碼塊:

      A_thread1_Async_Start: 14:44:20
      A_thread2_Async_Start: 14:44:20
      A_thread1_Async_End: 14:44:22
      A_thread2_Async_End: 14:44:22複製代碼
    • B 類線程訪問的方法中有同步代碼塊,B 類線程是同步的,一個線程在訪問對象的同步代碼塊,另外一個訪問對象的同步代碼塊的線程會被阻塞:

      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複製代碼
    • synchronized(this|object) {} 代碼塊 {} 以外的代碼依然是異步的:

      B_thread2_Sync1: 14:44:20
      B_thread1_Sync1: 14:44:20複製代碼
    • C 類線程訪問的是 synchronized 修飾非靜態方法,C 類線程是同步的,一個線程在訪問對象的同步代方法,另外一個訪問對象同步方法的線程會被阻塞:

      C_thread1_Sync2_Start: 14:44:20
      C_thread1_Sync2_End: 14:44:22
      C_thread2_Sync2_Start: 14:44:26
      C_thread2_Sync2_End: 14:44:28複製代碼
    • synchronized 修飾非靜態方法,做用範圍是整個方法,因此方法中全部的代碼都是同步的:

      C_thread1_Sync2: 14:44:20
      C_thread2_Sync2: 14:44:26複製代碼
    • 由結果可知 B 類和 C 類線程順序執行,類中 synchronized(this|object) {} 代碼塊和 synchronized 修飾非靜態方法獲取的鎖是同一個鎖,即該類的對象的對象鎖。因此 B 類線程和 C 類線程也是同步的:

      B_thread1_Sync1_Start: 14:44:22
      B_thread1_Sync1_End: 14:44:24
      C_thread1_Sync2_Start: 14:44:20
      C_thread1_Sync2_End: 14:44:22
      B_thread2_Sync1_Start: 14:44:24
      B_thread2_Sync1_End: 14:44:26
      C_thread2_Sync2_Start: 14:44:26
      C_thread2_Sync2_End: 14:44:28複製代碼

1.2 對於不一樣對象

  • 修改測試代碼爲:
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 修飾非靜態方法是異步的,同一個類的不一樣對象的對象鎖互不干擾。

2 獲取類鎖

2.1 對於同一對象

  • 同步線程類:
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複製代碼
  • 結果分析:

    • 由結果能夠看出,在同一對象的狀況下,synchronized(類.class) {} 代碼塊或 synchronized 修飾靜態方法和 synchronized(this|object) {} 代碼塊和 synchronized 修飾非靜態方法的行爲一致。

2.2 對於不一樣對象

  • 修改測試代碼爲:
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複製代碼
  • 結果分析:

    • 兩個線程訪問不一樣對象的 synchronized(類.class) {} 代碼塊或 synchronized 修飾靜態方法仍是同步的,類中 synchronized(類.class) {} 代碼塊和 synchronized 修飾靜態方法獲取的鎖是類鎖。對於同一個類的不一樣對象的類鎖是同一個。

3 類中同時有 synchronized(類.class) {} 代碼塊或 synchronized 修飾靜態方法和 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 修飾非靜態方法 */
    private synchronized void sync1() {
        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() {
        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) {
        Thread B_thread1 = new Thread(syncThread, "B_thread1");
        Thread C_thread1 = new Thread(syncThread, "C_thread1");
        B_thread1.start();
        C_thread1.start();
    }
}複製代碼
  • 運行結果:
B_thread1_Sync1_Start: 15:35:21
C_thread1_Sync2_Start: 15:35:21
B_thread1_Sync1_End: 15:35:23
C_thread1_Sync2_End: 15:35:23複製代碼
  • 運行結果分析:

    • 由結果能夠看到 B 類線程和 C 類線程是異步的,即 synchronized 修飾靜態方法和 synchronized 修飾非靜態方法是異步的,對於 synchronized(類.class) {} 代碼塊和 synchronized(this|object) {} 代碼塊也是同樣的。因此對象鎖和類鎖是獨立的,互不干擾。

4 補充

  1. synchronized關鍵字不能繼承。

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

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

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

相關文章
相關標籤/搜索