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

Java 多線程進階(二):鎖機制深度解析與生產者消費者實戰

     編輯:Java 綜合教學

Java 多線程進階(二):鎖機制深度解析與生產者消費者實戰

多線程並發核心挑戰在於線程同步(控制執行順序)與線程通信(共享數據傳遞)。Java 提供 synchronized、volatile、wait/notify 三大基石。本文通過單例模式破壞、生產消費無序兩個反面教材,引出鎖機制正確用法,並構建完整的生產者-消費者模型。

一、並發下的兩大經典事故

事故 1:單例模式線程不安全

class MySingle {
    private static MySingle instance = null;
    private MySingle() {}
    public static MySingle getInstance() {
        if (instance == null) { instance = new MySingle(); } // 非原子操作!
        return instance;
    }
}

現象:10 條線程並發調用,產生多個實例(hashCode 不一致)

根因:instance == null 檢查與 new MySingle() 非原子,多線程同時通過檢查

事故 2:生產消費無序交替

class Factory {
    public void createProduct(int i) { for (j=1..100) print; }
    public void sellProduct(int i)   { for (j=1..20) print; }
}

現象:生產線程與銷售線程輸出交織混亂,無法保證「生產一輪 → 銷售一輪」順序

二、Java 內存模型 (JMM) 與鎖的本質

  • 每個線程擁有私有棧,共享堆內存
  • 鎖是線程間內存可見性與執行順序的協調器
  • synchronized:互斥鎖 + 內存屏障(刷新工作內存到主存、失效其他線程緩存)
  • volatile:僅保證可見性與有序性,不保證原子性

JMM 內存模型

三、synchronized 完整實戰

1. 修復單例(雙重檢查鎖 DCL)

public static MySingle getInstance() {
    if (instance == null) {                    // 第一次檢查(無鎖,性能)
        synchronized (MySingle.class) {        // 類級別鎖
            if (instance == null) {            // 第二次檢查(有鎖,安全)
                instance = new MySingle();     // volatile 禁止指令重排 (JDK 5+)
            }
        }
    }
    return instance;
}
private static volatile MySingle instance = null; // 關鍵!

2. 生產者-消費者標準模板 (wait/notify)

class Factory {
    private boolean isProducing = true; // true=生產中, false=銷售中

    public synchronized void createProduct(int round) {
        while (!isProducing) {             // 條件不滿足 → 等待
            try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); }
        }
        for (int j = 1; j <= 100; j++) {
            System.out.println("第" + round + "輪生產,產出" + j + "件");
        }
        isProducing = false;               // 狀態翻轉
        this.notify();                     // 喚醒消費者
    }

    public synchronized void sellProduct(int round) {
        while (isProducing) {
            try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); }
        }
        for (int j = 1; j <= 20; j++) {
            System.out.println("第" + round + "輪銷售,銷售" + j + "件");
        }
        isProducing = true;
        this.notify();                     // 喚醒生產者
    }
}

關鍵點:

  1. synchronized 修飾方法 → 隱式鎖 this
  2. while 而非 if 判斷條件 → 防止虛假喚醒
  3. wait() 釋放鎖並進入等待隊列;notify() 喚醒一個等待線程
  4. 狀態變量 isProducing 受鎖保護,保證可見性

四、volatile 深度解析與陷阱

class NumberRange {
    private volatile int counter = 0;
    public void increment() { counter++; } // 非原子!讀-改-寫三步
}

編譯器視角等價代碼:

public synchronized int getCounter() { return counter; }
public synchronized void setCounter(int v) { counter = v; }
public int increment() {
    int temp = getCounter();  // 讀
    temp = temp + 1;          // 改
    setCounter(temp);         // 寫
    return temp;
}

結論:volatile 只能保證單次讀/寫可見,**複合操作(如 ++、+=)仍需 synchronized 或 AtomicInteger**

五、進階:Lock 接口與 Condition (JDK 5+)

class FactoryLock {
    private final Lock lock = new ReentrantLock();
    private final Condition notFull = lock.newCondition();
    private final Condition notEmpty = lock.newCondition();
    private final Queue queue = new LinkedList<>();
    private final int capacity = 100;

    public void produce(int item) throws InterruptedException {
        lock.lock();
        try {
            while (queue.size() == capacity) notFull.await();
            queue.offer(item);
            notEmpty.signal();
        } finally { lock.unlock(); }
    }

    public int consume() throws InterruptedException {
        lock.lock();
        try {
            while (queue.isEmpty()) notEmpty.await();
            int item = queue.poll();
            notFull.signal();
            return item;
        } finally { lock.unlock(); }
    }
}

優勢:多條件變量精準喚醒、可中斷鎖、公平鎖選項、超時獲取鎖

六、最佳實踐清單

場景推薦方案避坑
簡單互斥/狀態翻轉synchronized 方法/塊鎖粒度過大(鎖整個方法而非關鍵區)
單一變量可見性volatile + 原子操作用於複合操作 (count++)
計數器/序列號AtomicLong / LongAdder自行 synchronized 實現
生產消費/有界隊列ArrayBlockingQueue / Lock+Condition手寫 wait/notify 易漏喚醒
讀多寫少ReentrantReadWriteLock寫多場景性能反而下降
「並發編程的藝術在於:用最小的鎖,保護最關鍵的數據,實現最大程度的並行。」
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved