首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 多線程中 synchronized 關鍵字的實戰應用詳解

     編輯:Java 相關知識

前言

在 Java 5 之前,synchronized 是實現線程同步的核心機制。雖然後來引入了 Lock 接口,但 synchronized 依然是最基礎、最常用的同步手段。本文通過豐富的代碼示例,深入剖析 synchronized 修飾方法與代碼塊的行為差異,幫助開發者精準掌握鎖的粒度與作用範圍。

核心概念:鎖的對象與臨界區

synchronized 的關鍵在於「鎖住什麼對象」:

  • 非靜態方法:鎖住當前實例(this)
  • 靜態方法:鎖住類的 Class 對象
  • 同步代碼塊:顯式指定鎖對象(如 synchronized(this) 或 synchronized(MyClass.class))

同一把鎖同一時間只允許一個線程進入臨界區,其他線程進入等待池。JVM 通過內存屏障保證:進入同步塊時從主內存讀取數據,退出前將修改刷回主內存,確保可見性。

實例一:同步非靜態方法

public class SynchronizedTest1 extends Thread {
    private synchronized void testSynchronizedMethod() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + " testSynchronizedMethod:" + i);
            try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }
        }
    }
    @Override public void run() { testSynchronizedMethod(); }
    public static void main(String[] args) {
        SynchronizedTest1 t = new SynchronizedTest1();
        t.start();           // 子線程
        t.testSynchronizedMethod(); // 主線程
    }
}

輸出:主線程先完整執行 0~9,子線程才開始執行。因為兩者共用同一實例鎖。

實例二:不同實例導致同步失效

public static void main(String[] args) {
    Thread t = new SynchronizedTest1(); t.start();
    Thread t1 = new SynchronizedTest1(); t1.start();
}

輸出:兩線程交替執行,同步失效。原因:兩個 SynchronizedTest1 實例擁有各自的鎖。

實例三:靜態方法同步修正

private static synchronized void testSynchronizedMethod() { ... }

改為靜態方法後,鎖升級為類鎖(SynchronizedTest1.class),兩線程重新同步執行。

同步代碼塊:更細粒度的控制

使用 this 鎖住實例

private void testSynchronizedBlock() {
    synchronized (this) { ... }
}

使用 Class 對象實現跨實例同步

private void testSynchronizedBlock() {
    synchronized (SynchronizedTest2.class) { ... }
}

此時即使創建多個實例,線程間也能同步,因為共享同一把類鎖。

進階場景解析

場景表現原因
兩線程訪問同一對象的同步方法串行執行共享實例鎖
兩線程訪問不同對象的同步方法並發執行各自持有不同鎖
訪問同步靜態方法全局串行共享 Class 鎖
一線程訪問同步方法,另一線程訪問非同步方法並發執行非同步方法不獲取鎖
同一對象的不同同步方法串行執行共享同一把實例鎖

最佳實踐建議

  1. 縮小鎖範圍:優先使用同步代碼塊而非整個方法,減少阻塞時間。
  2. 明確鎖對象:避免使用 String、包裝類等不可變對象作為鎖(常量池可能導致意外共享)。
  3. 避免死鎖:多把鎖時保持一致的獲取順序。
  4. 考慮替代方案:高併發場景下可嘗試 ReentrantLock、StampedLock 或無鎖數據結構。

總結

synchronized 是 Java 併發編程的基石。理解「誰持有鎖、鎖住什麼、保護哪段代碼」是寫出正確高效同步代碼的前提。通過本文的實例對比,相信讀者能靈活運用方法鎖、代碼塊鎖、實例鎖與類鎖,應對各種併發場景。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved