當多執行緒同時存取共享可變資料時,若缺乏同步機制,會發生競態條件,導致資料不一致。本文以銀行帳戶存提款為例,示範問題重現與 synchronized 解決方案。
class Account {
private String name;
private float balance;
public Account(String name, float balance) {
this.name = name;
this.balance = balance;
}
// 存款:非同步
public void deposit(float amount) {
float tmp = balance;
tmp += amount;
sleep(100); // 模擬資料庫操作延遲
balance = tmp;
}
// 提款:非同步
public void withdraw(float amount) {
float tmp = balance;
tmp -= amount;
sleep(100);
balance = tmp;
}
public float getBalance() { return balance; }
private void sleep(long ms) {
try { Thread.sleep(ms); } catch (InterruptedException ignored) {}
}
}
// 測試:1000 執行緒各存 100 再提 100,預期餘額 1000
public class AccountTest {
private static final int THREADS = 1000;
public static void main(String[] args) throws InterruptedException {
Account acc = new Account("John", 1000f);
Thread[] threads = new Thread[THREADS];
for (int i = 0; i < THREADS; i++) {
threads[i] = new Thread(() -> {
acc.deposit(100f);
acc.withdraw(100f);
});
threads[i].start();
}
for (Thread t : threads) t.join();
System.out.println("最終餘額: " + acc.getBalance()); // 非 1000!
}
}
最終餘額: 3900.0
最終餘額: 4900.0
最終餘額: 4700.0
最終餘額: 5200.0
balance = tmp 非單一指令,中間可被切換執行緒。balance 為競爭資源。public synchronized void deposit(float amount) {
float tmp = balance;
tmp += amount;
sleep(1); // 縮短睡眠仍會觸發競態
balance = tmp;
}
public synchronized void withdraw(float amount) {
float tmp = balance;
tmp -= amount;
sleep(1);
balance = tmp;
}
機制:每個物件擁有內建鎖(monitor)。呼叫 synchronized 方法需取得該物件鎖,釋放後其他執行緒才能進入。
public void deposit(float amount) {
synchronized (this) { // 或 synchronized(lockObject)
float tmp = balance;
tmp += amount;
sleep(1);
balance = tmp;
}
}
加上 synchronized 後,多次執行皆輸出:
最終餘額: 1000.0
float(32 位元)在 JVM 為原子操作(JLS 17.7)。long/double(64 位元),32 位元 JVM 可能非原子,需 volatile 或同步。AtomicReference/AtomicLong,確保可見性與一致性。| 工具 | 適用場景 |
|---|---|
ReentrantLock | 需可中斷、超時、公平鎖、條件變數 |
ReadWriteLock | 讀多寫少,提升並發度 |
AtomicInteger/Long/Reference | 單一變數無鎖原子操作 |
ConcurrentHashMap | 高並發 Map |
CountDownLatch/CyclicBarrier | 執行緒協調等待 |
Semaphore | 資源池許可控制 |
synchronized 簡單場景,Lock 複雜需求。tryLock(timeout)。