悲觀鎖和樂觀鎖

捞月亮的小北發表於2024-07-07

在Java中,悲觀鎖和樂觀鎖是處理併發訪問共享資源時採用的不同策略。它們主要的區別在於對資料競爭的預期和處理方式。

悲觀鎖 (Pessimistic Lock)

悲觀鎖基於“悲觀”的假設,即預設情況下它認為資料可能會被其他執行緒修改,因此在運算元據前會嘗試獲得獨佔的鎖。一旦某個執行緒持有悲觀鎖,其他試圖訪問相同資源的執行緒將被阻塞,直到鎖被釋放。

概念和作用:
悲觀鎖透過確保在任何時刻只有一個執行緒能夠訪問共享資源,從而避免了資料的競爭條件。它保證了資料的一致性和完整性,但是可能會降低系統的整體效能,因為等待鎖的執行緒會被阻塞。

示例程式碼:

public class PessimisticLockExample {
    public static void main(String[] args) {
        Object lock = new Object();
    
        Thread thread1 = new Thread(() -> {
            synchronized (lock) { // 悲觀鎖
                System.out.println("Thread 1: Acquired lock");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("Thread 1: Releasing lock");
            }
        });
    
        Thread thread2 = new Thread(() -> {
            synchronized (lock) { // 悲觀鎖
                System.out.println("Thread 2: Attempting to acquire lock");
            }
        });
    
        thread1.start();
        thread2.start();
    }
}

在這個例子中,thread2​在thread1​釋放鎖之前將被阻塞,無法執行其程式碼。

樂觀鎖 (Optimistic Lock)

樂觀鎖基於“樂觀”的假設,即預設情況下它認為資料不會被其他執行緒修改,因此在運算元據時不立即加鎖。當需要更新資料時,它會檢查在此期間資料是否已經被其他執行緒修改過。如果資料未被修改,則更新成功;如果資料已被修改,則更新失敗,並可能觸發重試。

概念和作用:
樂觀鎖主要用於減少鎖帶來的效能開銷,尤其是在讀操作遠多於寫操作的場景下。它透過避免不必要的鎖爭用來提高併發效能,但這也意味著在資料確實被其他執行緒修改時,操作可能會失敗。

實現方式:
樂觀鎖可以透過版本號機制或CAS(Compare and Swap)演算法來實現。

示例程式碼:

import java.util.concurrent.atomic.AtomicInteger;

public class OptimisticLockExample {
    public static void main(String[] args) {
        AtomicInteger counter = new AtomicInteger(0); // 使用樂觀鎖
    
        Thread thread1 = new Thread(() -> {
            int oldValue = counter.get();
            // 假設這裡有一段長的計算過程...
            int newValue = oldValue + 1;
            boolean success = counter.compareAndSet(oldValue, newValue); // CAS操作
            if (success) {
                System.out.println("Thread 1: Incremented value to " + newValue);
            } else {
                System.out.println("Thread 1: Failed to increment value.");
            }
        });
    
        Thread thread2 = new Thread(() -> {
            int oldValue = counter.get();
            // 假設這裡有一段長的計算過程...
            int newValue = oldValue + 1;
            boolean success = counter.compareAndSet(oldValue, newValue); // CAS操作
            if (success) {
                System.out.println("Thread 2: Incremented value to " + newValue);
            } else {
                System.out.println("Thread 2: Failed to increment value.");
            }
        });
    
        thread1.start();
        thread2.start();
    }
}

在這個例子中,AtomicInteger​的compareAndSet​方法是一個CAS操作,它實現了樂觀鎖。如果兩個執行緒同時嘗試增加計數器的值,只有一個執行緒的更新會成功,另一個執行緒的更新會失敗,因為它檢測到資料已經發生了變化。

相關文章