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

Java 多線程實例詳解(三):線程安全與同步機制

     編輯:Java 相關知識

前言

本系列最終篇,聚焦線程安全核心問題:共享可變狀態下的數據競爭,系統講解 synchronized、Lock、wait/notify、Condition 及生產者消費者模式完整實現。

一、線程安全經典反例:銀行取款

class Account { double balance = 1000; }
class Withdraw implements Runnable {
    Account acc; double amount;
    public void run() {
        if (acc.balance >= amount) { // 檢查
            sleep(10);               // 模擬網絡延遲
            acc.balance -= amount;   // 扣款
            System.out.println(Thread.currentThread().getName() + " 取款 " + amount + " 餘額 " + acc.balance);
        }
    }
}
// 兩線程同時取款 700,可能雙雙通過檢查,導致餘額 -400
new Thread(new Withdraw(acc, 700), "A").start();
new Thread(new Withdraw(acc, 700), "B").start();

根因:檢查-執行非原子,CPU 任意切換導致數據不一致。

二、同步解決方案

1. synchronized 方法

public synchronized void withdraw(double amt) {
    if (balance >= amt) { balance -= amt; }
}

2. 同步代碼塊(細粒度鎖)

public void withdraw(double amt) {
    synchronized (this) { // 或私有 final Object lock = new Object();
        if (balance >= amt) { balance -= amt; }
    }
}

3. Lock 顯式鎖

private final Lock lock = new ReentrantLock();
public void withdraw(double amt) {
    lock.lock();
    try { if (balance >= amt) balance -= amt; }
    finally { lock.unlock(); }
}

鎖選擇原則:實例資源鎖 this 或專用 lock;靜態資源鎖 ClassName.class;**嚴禁**鎖字串常量、基本型別包裝對象(常量池共享導致誤鎖)。

三、線程通訊:wait/notify vs Condition

特性Object 監視器Lock Condition
綁定鎖synchronized(obj)lock.newCondition()
等待隊列單一多個(如 notEmpty/notFull)
喚醒精度notify() 隨機/notifyAll() 全部signal() 隨機/signalAll() 全部
中斷響應不支援await() 可響應中斷
使用難度低中(需手動 lock/unlock)

wait/notify 經典:生產者-消費者

class Buffer {
    private int[] items = new int[10];
    private int count = 0, in = 0, out = 0;
    public synchronized void produce(int value) throws InterruptedException {
        while (count == items.length) wait(); // 滿等待
        items[in] = value; in = (in + 1) % items.length; count++;
        notifyAll(); // 唤醒消費者
    }
    public synchronized int consume() throws InterruptedException {
        while (count == 0) wait(); // 空等待
        int val = items[out]; out = (out + 1) % items.length; count--;
        notifyAll(); // 唤醒生產者
        return val;
    }
}

關鍵點:

  1. 用 while 而非 if 檢查條件(防虛假喚醒)。
  2. wait() 釋放鎖並進入等待集;notifyAll() 移至入口集競爭鎖。
  3. 喚醒後需重新獲得鎖才能繼續執行。

Condition 版(更靈活)

class Buffer {
    private final Lock lock = new ReentrantLock();
    private final Condition notFull = lock.newCondition(), notEmpty = lock.newCondition();
    // produce/consume 同前,將 wait→notFull.await(), notifyAll→notEmpty.signalAll() 等
}

四、總結與最佳實踐

  1. 首選無鎖:ThreadLocal、Atomic*、ConcurrentHashMap、BlockingQueue。
  2. 次選 synchronized:代碼簡潔,JVM 優化完善(偏向鎖、輕量級鎖、自旋、消除)。
  3. 需要高級特性 時用 Lock:可中斷、超時、公平、多條件。
  4. 生產者消費者 直接用 ArrayBlockingQueue / LinkedBlockingQueue,無需手寫。
  5. 避免:鎖粒度過大、雙重檢查鎖單例(用 enum 或 static holder)、死鎖(固定鎖順序、tryLock 超時)。

掌握同步原理與工具選型,是編寫高併發、高穩定 Java 系統的必備功力。

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