Java 執行緒同步 (Synchronization)

當多個執行緒存取共享資源時,需要同步來避免競爭條件 (Race Condition)。

問題:競爭條件

public class Counter {
    private int count = 0;

    public void increment() {
        count++;  // 不是原子操作!
    }

    public int getCount() {
        return count;
    }
}

// 多執行緒同時呼叫 increment() 會有問題
Counter counter = new Counter();

Thread t1 = new Thread(() -> {
    for (int i = 0; i < 10000; i++) counter.increment();
});

Thread t2 = new Thread(() -> {
    for (int i = 0; i < 10000; i++) counter.increment();
});

t1.start();
t2.start();
t1.join();
t2.join();

// 結果可能不是 20000!
System.out.println(counter.getCount());

synchronized 方法

public class Counter {
    private int count = 0;

    // 同步方法
    public synchronized void increment() {
        count++;
    }

    public synchronized int getCount() {
        return count;
    }
}

synchronized 區塊

更細粒度的控制:

public class Counter {
    private int count = 0;
    private final Object lock = new Object();

    public void increment() {
        synchronized (lock) {
            count++;
        }
    }

    public int getCount() {
        synchronized (lock) {
            return count;
        }
    }
}

使用 this 作為鎖

public class Example {
    public void method() {
        synchronized (this) {
            // 等同於 synchronized 方法
        }
    }
}

靜態同步

public class StaticCounter {
    private static int count = 0;

    // 鎖定的是 Class 物件
    public static synchronized void increment() {
        count++;
    }

    // 等同於
    public static void increment2() {
        synchronized (StaticCounter.class) {
            count++;
        }
    }
}

Lock 介面

比 synchronized 更靈活:

import java.util.concurrent.locks.*;

public class Counter {
    private int count = 0;
    private final Lock lock = new ReentrantLock();

    public void increment() {
        lock.lock();
        try {
            count++;
        } finally {
            lock.unlock();  // 確保解鎖
        }
    }
}

tryLock

嘗試獲取鎖,不會阻塞:

if (lock.tryLock()) {
    try {
        // 取得鎖,執行操作
    } finally {
        lock.unlock();
    }
} else {
    // 無法取得鎖
}

// 帶有逾時
if (lock.tryLock(1, TimeUnit.SECONDS)) {
    // ...
}

ReadWriteLock

讀寫分離:

ReadWriteLock rwLock = new ReentrantReadWriteLock();
Lock readLock = rwLock.readLock();
Lock writeLock = rwLock.writeLock();

// 讀取(可以多個執行緒同時讀)
readLock.lock();
try {
    // 讀取操作
} finally {
    readLock.unlock();
}

// 寫入(獨占)
writeLock.lock();
try {
    // 寫入操作
} finally {
    writeLock.unlock();
}

原子類別

import java.util.concurrent.atomic.*;

AtomicInteger count = new AtomicInteger(0);

count.incrementAndGet();  // ++count
count.getAndIncrement();  // count++
count.addAndGet(5);       // count += 5
count.compareAndSet(5, 10);  // CAS 操作

常用原子類別:

  • AtomicIntegerAtomicLongAtomicBoolean
  • AtomicReference<T>
  • AtomicIntegerArrayAtomicLongArray

死鎖

兩個執行緒互相等待對方的鎖:

// 死鎖範例
Object lock1 = new Object();
Object lock2 = new Object();

Thread t1 = new Thread(() -> {
    synchronized (lock1) {
        Thread.sleep(100);
        synchronized (lock2) {
            // ...
        }
    }
});

Thread t2 = new Thread(() -> {
    synchronized (lock2) {
        Thread.sleep(100);
        synchronized (lock1) {
            // ...
        }
    }
});
// t1 等待 lock2,t2 等待 lock1 → 死鎖

避免死鎖

  1. 固定鎖的取得順序
  2. 使用 tryLock 設定逾時
  3. 避免巢狀鎖

synchronized vs Lock

特性synchronizedLock
自動釋放否(需要 finally)
中斷等待
嘗試取鎖是(tryLock)
公平性可設定
多條件一個多個 Condition

Condition

比 wait/notify 更靈活:

Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();

// 等待
lock.lock();
try {
    while (!ready) {
        condition.await();  // 類似 wait()
    }
} finally {
    lock.unlock();
}

// 通知
lock.lock();
try {
    ready = true;
    condition.signalAll();  // 類似 notifyAll()
} finally {
    lock.unlock();
}