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

Java多線程編程基礎(三):synchronized、wait/notify、內存模型與線程協作核心

     編輯:Java 相關知識

前言:同步是多線程正確性的基石

多線程共享可變狀態時,原子性、可見性、有序性三大特性若無保障,必現數據競爭、指令重排、內存可見性失效等並發Bug。Java提供synchronized、volatile、wait/notify、Lock/Condition、原子類等機制。本文深度解析核心同步原語與Java內存模型 (JMM)。

一、 互斥鎖:synchronized 關鍵字

1. 三種鎖定目標


// ① 實例方法鎖:this
public synchronized void instanceMethod() { ... }

// ② 靜態方法鎖:Class對象
public static synchronized void staticMethod() { ... }

// ③ 同步塊:顯式指定鎖對象
public void blockSync(Object lock) {
    synchronized (lock) { ... } // 鎖住lock對象
}

2. 鎖對象選擇原則

3. 鎖升級與優化 (HotSpot)

無鎖 → 偏向鎖 → 輕量級鎖 (CAS) → 重量級鎖 (OS互斥量)
     (單線程)    (低競爭)         (高競爭)
  • 偏向鎖:首次獲得線程標記對象頭,後續無CAS開銷。撤銷需全局安全點
  • 輕量級鎖:CAS將Mark Word指向線程棧Lock Record,自旋等待
  • 重量級鎖:ObjectMonitor,OS互斥量,線程阻塞/喚醒

二、 線程協作:wait/notify/notifyAll — Object方法,非Thread方法

核心規則(必須牢記)

  1. 只能在同步塊/方法中調用:持有鎖對象的監視器
  2. 調用wait()釋放鎖,進入等待集 (WaitSet),線程狀態WAITING/TIMED_WAITING
  3. 調用notify()/notifyAll()不釋放鎖,喚醒等待集中線程,喚醒線程需重新競爭鎖
  4. 虛假喚醒:線程可能無通知自動醒來,**必須用while(condition)循環檢查**

標準協作模板


class BoundedQueue {
    private final List queue = new ArrayList<>();
    private final int capacity;

    public synchronized void put(T item) throws InterruptedException {
        while (queue.size() == capacity) wait(); // while而非if!
        queue.add(item);
        notifyAll(); // 喚醒消費者
    }

    public synchronized T take() throws InterruptedException {
        while (queue.isEmpty()) wait();
        T item = queue.remove(0);
        notifyAll(); // 喚醒生產者
        return item;
    }
}

notify vs notifyAll 選擇

  • notify():隨機喚醒一個,**僅當所有等待線程等待同一條件、且喚醒任意一個都能正確處理時使用**(極少見)
  • notifyAll():喚醒所有,**默認首選**,避免信號丟失、死鎖、活性失效

三、 Java內存模型 (JMM) — 並發正確性的契約

三大特性

鎖對象保護範圍典型場景風險
this當前實例所有同步方法/塊實例狀態保護外部代碼可鎖住實例導致死鎖/活性失效
Class.class所有實例共享(類級)靜態狀態、單例初始化粒度粗、影響所有實例
私有final對象僅內部可訪問推薦:private final Object lock = new Object();無外部干擾、封裝性最好
字符串常量JVM全局共享(常量池)極度危險無關代碼共用同一鎖 → 嚴重競爭/死鎖
局部變量new Object()每次調用新對象無效每次鎖不同對象,失去互斥
特性定義保障機制
原子性操作不可分割synchronized/Lock/原子類/volatile(僅長整型/雙精度非原子)
可見性一線程寫入,他線程可見volatile/synchronized/final/Lock
有序性程序順序與執行順序一致volatile/synchronized/final/happens-before規則

happens-before 核心規則 (JSR-133)

  1. 程序順序:單線程內按代碼順序
  2. 鎖規則:unlock happens-before 同鎖後續 lock
  3. volatile規則:寫 volatile happens-before 讀同 volatile
  4. 線程啟動:Thread.start() happens-before 線程內任意操作
  5. 線程終止:線程內操作 happens-before Thread.join() 返回
  6. 中斷規則:interrupt() happens-before 被中斷線程檢測到中斷
  7. final規則:構造器寫 final 域 happens-before 對象引用可見
  8. 傳遞性:A hb B, B hb C → A hb C

指令重排約束

編譯器/CPU可重排指令,**但不改變單線程語義**。volatile/synchronized插入內存屏障禁止特定重排:

  • volatile寫:StoreStore + StoreLoad 屏障
  • volatile讀:LoadLoad + LoadStore 屏障
  • synchronized進/出:全屏障

四、 volatile 關鍵字:輕量級同步


class VolatileDemo {
    volatile boolean stopped = false; // 可見性保證
    long counter = 0; // 非volatile,64位非原子!

    void run() {
        while (!stopped) counter++; // 編譯器可能將stopped讀取提取出循環!volatile阻止
    }
    void stop() { stopped = true; } // 寫入立即對其他線程可見
}

適用場景:狀態標誌、雙重檢查鎖單例、獨立觀察者模式。**不適用**:複合操作(count++)、不變量維護(需鎖/原子類)。

五、 經典生產者-消費者完整實現


class ProdConQueue {
    private final T[] items;
    private int putIdx = 0, takeIdx = 0, count = 0;
    private final Object notFull = new Object();
    private final Object notEmpty = new Object();

    @SuppressWarnings("unchecked")
    public ProdConQueue(int capacity) { items = (T[]) new Object[capacity]; }

    public void put(T item) throws InterruptedException {
        synchronized (notFull) {
            while (count == items.length) notFull.wait();
            items[putIdx] = item;
            putIdx = (putIdx + 1) % items.length;
            count++;
            synchronized (notEmpty) { notEmpty.notifyAll(); }
        }
    }

    public T take() throws InterruptedException {
        synchronized (notEmpty) {
            while (count == 0) notEmpty.wait();
            T item = items[takeIdx];
            items[takeIdx] = null;
            takeIdx = (takeIdx + 1) % items.length;
            count--;
            synchronized (notFull) { notFull.notifyAll(); }
            return item;
        }
    }
}

雙鎖設計:notFull/notEmpty分離生產者/消費者等待集,減少競爭。實際生產用ArrayBlockingQueue/LinkedBlockingQueue。

六、 總結與下一站

本文掌握核心同步原語:

  1. synchronized:監視器鎖、三種鎖對象、升級優化、私有final對象最佳
  2. wait/notify:Object方法、同步塊內調用、wait釋放鎖、notify不釋放、while循環檢查、notifyAll首選
  3. JMM happens-before:八大規則構建可見性/有序性保證網
  4. volatile:可見性+有序性、無原子性、雙重檢查鎖、狀態標誌

下一站:JUC並發包核心——Lock/Condition、AQS原理、原子類、並發集合、線程池、CompletableFuture、虛擬線程。

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