JavaEE:多線程進階

JavaEE:多線程進階

  • 一、對比不同鎖策略之間的應用場景及其區別
    • 1. 悲觀鎖 和 樂觀鎖
      • 1.1 定義和原理
      • 1.2 應用場景
      • 1.3 示例代碼
    • 2. 重量級鎖 和 輕量級鎖
      • 2.1 定義和原理
      • 2.2 應用場景
      • 2.3 示例代碼
    • 3. 掛起等待鎖 和 自旋鎖
      • 3.1 定義和原理
      • 3.2 應用場景
      • 3.3 示例代碼
    • 4. 幾對鎖之間的聯系
    • 5. 總結
  • 二、synchronized 的優化策略
    • 1. 鎖升級(Lazy Initialization)
      • 1.1 定義和原理
      • 1.2 鎖的狀態轉換圖
      • 1.3 應用場景
      • 1.4 示例代碼
    • 2. 鎖消除(Lock Elimination)
      • 2.1 定義和原理
      • 2.2 示例代碼
    • 3. 鎖粗化(Lock Coarsening)
      • 3.1 定義和原理
      • 3.2 示例代碼
    • 4. 幾種優化策略的聯系
  • 三、鎖策略2
    • 1. 可重入鎖 和 不可重入鎖
      • 1.1 定義和原理
      • 1.2 應用場景
      • 1.3 示例代碼
      • 1.4 圖文并茂
        • 1. 初始狀態:線程 A 獲取鎖
        • 2. 遞歸調用:線程 A 再次嘗試獲取鎖
        • 3. 結束狀態:線程 A 釋放鎖
    • 2. 公平鎖 和 非公平鎖
      • 2.1 定義和原理
      • 2.2 應用場景
      • 2.3 示例代碼
      • 2.4 圖文并茂
        • 1. 公平鎖流程圖
          • 閱讀順序:
        • 2. 非公平鎖流程圖
          • 閱讀順序:
    • 3. 總結
  • 四、CAS(Compare-And-Swap)問題
    • 1. 通過偽代碼逐步引出 CAS 問題
      • 1.1 簡單變量更新
      • 1.2 引入無鎖更新的想法
      • 1.3 引入比較-交換的思想
      • 1.4 引出 CAS 操作的偽代碼
      • 1.5 討論 CAS 操作的基本特性
      • 1.6 總結 CAS 操作的應用場景
    • 2. 通過 CAS 操作實現原子類
      • 2.1 定義問題
      • 2.2 引入無鎖更新的想法
      • 2.3 引出 CAS 操作的偽代碼
      • 2.4 實現真實的 CAS 操作
      • 2.5 分析代碼的工作原理
      • 2.6 圖文并茂說明
      • 2.7 總結
    • 3. 通過 CAS 操作實現自旋鎖
      • 3.1 定義問題
      • 3.2 引入無鎖更新的想法
      • 3.3 引出 CAS 操作的偽代碼
      • 3.4 實現真實的 CAS 操作
      • 3.5 分析代碼的工作原理
      • 3.6 圖文并茂說明
      • 3.7 總結
    • 4. CAS 中 ABA 問題
      • 4.1 詳細介紹 CAS 中的 ABA 問題
      • 4.2 輔助代碼示例
      • 4.3 圖文并茂理解 ABA 問題
      • 4.4 解決 ABA 問題的方法
      • 4.5 總結

一、對比不同鎖策略之間的應用場景及其區別

在 Java 中,鎖策略的選擇對于多線程程序的性能和正確性至關重要。不同的鎖策略適用于不同的應用場景,理解它們的區別和聯系有助于編寫高效且線程安全的代碼。

1. 悲觀鎖 和 樂觀鎖

1.1 定義和原理

  • 悲觀鎖(Pessimistic Locking) :假設沖突會發生,因此在每次訪問共享資源都加鎖,確保只有一個線程能夠訪問到資源。典型的實現包括 synchronizedReentrantLock
  • 樂觀鎖(Optimistic Locking):假設沖突不會發生,只有在真正發生沖突的時候才采取措施。樂觀鎖通常通過版本號和時間戳來檢測沖突。典型實現包括 CAS(Compare And Swap)操作。

1.2 應用場景

  • 悲觀鎖:適用于高并發環境的寫操作頻繁的場景。例如數據庫事務、文件系統等。
  • 樂觀鎖:適用于讀操作遠多于寫操作場景。例如緩存,分布式系統中的數據一致性。

1.3 示例代碼

// 悲觀鎖示例:使用 synchronized
public class PessimisticLockExample {private int count = 0;public synchronized void increment() {count++;}public static void main(String[] args) throws InterruptedException {PessimisticLockExample example = new PessimisticLockExample();Thread t1 = new Thread(() -> {for (int i = 0; i < 10000; i++) {example.increment();}});Thread t2 = new Thread(() -> {for (int i = 0; i < 10000; i++) {example.increment();}});t1.start();t2.start();t1.join();t2.join();System.out.println("Final count: " + example.count);}
}
// 樂觀鎖示例:使用 Atomic 類
import java.util.concurrent.atomic.AtomicInteger;public class OptimisticLockExample {private AtomicInteger count = new AtomicInteger(0);public void increment() {count.incrementAndGet();}public static void main(String[] args) throws InterruptedException {OptimisticLockExample example = new OptimisticLockExample();Thread t1 = new Thread(() -> {for (int i = 0; i < 10000; i++) {example.increment();}});Thread t2 = new Thread(() -> {for (int i = 0; i < 10000; i++) {example.increment();}});t1.start();t2.start();t1.join();t2.join();System.out.println("Final count: " + example.count.get());}
}

2. 重量級鎖 和 輕量級鎖

2.1 定義和原理

  • 重量級鎖(Heavyweight Locking):基于操作系統級別的同步機制(如 mutex),當多個線程競爭同一把鎖時,線程就會掛起等待和上下文切換,開銷較大。
  • 輕量級鎖(Lightweight Locking):通過 CAS 操作嘗試獲取鎖,避免重量級鎖的開銷。輕量級鎖適用于低競爭的場景。

2.2 應用場景

  • 重量級鎖: 適用于高競爭的場景。例如數據庫連接池和文件系統等。
  • 輕量級鎖: 適用于低競爭的場景。例如緩存和計數器等。

2.3 示例代碼

// 重量級鎖示例:使用 ReentrantLock
import java.util.concurrent.locks.ReentrantLock;public class HeavyweightLockExample {private final ReentrantLock lock = new ReentrantLock();private int count = 0;public void increment() {lock.lock();try {count++;} finally {lock.unlock();}}public static void main(String[] args) throws InterruptedException {HeavyweightLockExample example = new HeavyweightLockExample();Thread t1 = new Thread(() -> {for (int i = 0; i < 10000; i++) {example.increment();}});Thread t2 = new Thread(() -> {for (int i = 0; i < 10000; i++) {example.increment();}});t1.start();t2.start();t1.join();t2.join();System.out.println("Final count: " + example.count);}
}
// 輕量級鎖示例:使用 Atomic 類
import java.util.concurrent.atomic.AtomicInteger;public class LightweightLockExample {private AtomicInteger count = new AtomicInteger(0);public void increment() {count.incrementAndGet();}public static void main(String[] args) throws InterruptedException {LightweightLockExample example = new LightweightLockExample();Thread t1 = new Thread(() -> {for (int i = 0; i < 10000; i++) {example.increment();}});Thread t2 = new Thread(() -> {for (int i = 0; i < 10000; i++) {example.increment();}});t1.start();t2.start();t1.join();t2.join();System.out.println("Final count: " + example.count.get());}
}

3. 掛起等待鎖 和 自旋鎖

3.1 定義和原理

  • 掛起等待鎖(Blocking Lock):當線程無法獲取鎖時,就會掛起并進入等待狀態,直到鎖釋放。適用于長時間持有鎖的場景。
  • 自旋鎖(Spin Lock):當線程無法獲取鎖時,會不斷地嘗試獲取鎖并不會進入等待狀態。適用于短時間持有鎖的場景。

3.2 應用場景

  • 掛起等待鎖:適用于鎖持有時間長的場景。例如數據庫查詢,文件處理等。
  • 自旋鎖:適用于鎖持有時間短的場景。例如緩存更新、計數器增加等。

3.3 示例代碼

// 掛起等待鎖示例:使用 ReentrantLock
import java.util.concurrent.locks.ReentrantLock;public class BlockingLockExample {private final ReentrantLock lock = new ReentrantLock();private int count = 0;public void increment() {lock.lock();try {count++;} finally {lock.unlock();}}public static void main(String[] args) throws InterruptedException {BlockingLockExample example = new BlockingLockExample();Thread t1 = new Thread(() -> {for (int i = 0; i < 10000; i++) {example.increment();}});Thread t2 = new Thread(() -> {for (int i = 0; i < 10000; i++) {example.increment();}});t1.start();t2.start();t1.join();t2.join();System.out.println("Final count: " + example.count);}
}
// 自旋鎖示例:使用 SpinLock
public class SpinLock {private boolean isLocked = false;public synchronized void lock() {while (isLocked) {// 自旋等待}isLocked = true;}public synchronized void unlock() {isLocked = false;}
}public class SpinLockExample {private final SpinLock lock = new SpinLock();private int count = 0;public void increment() {lock.lock();try {count++;} finally {lock.unlock();}}public static void main(String[] args) throws InterruptedException {SpinLockExample example = new SpinLockExample();Thread t1 = new Thread(() -> {for (int i = 0; i < 10000; i++) {example.increment();}});Thread t2 = new Thread(() -> {for (int i = 0; i < 10000; i++) {example.increment();}});t1.start();t2.start();t1.join();t2.join();System.out.println("Final count: " + example.count);}
}

4. 幾對鎖之間的聯系

  • 悲觀鎖 VS 樂觀鎖:悲觀鎖適用于高競爭場景,樂觀鎖適用于低競爭場景。兩者都可以使用版本號和 CAS 等機制來結合使用。
  • 重量級鎖 VS 輕量級鎖:重量級鎖適用于高競爭場景,輕量級鎖適用于低競爭場景。JVM 的子自適應鎖機制根據實際情況動態調整鎖的狀態。
  • 掛起等待鎖 VS 自旋鎖:掛起等待鎖適用于鎖持有時間長的場景,自旋鎖適用于鎖持有時間短的場景。兩者可以根據鎖持有的時間來選擇使用。

5. 總結

  • 悲觀鎖:適用于高競爭場景,線程安全但開銷較大。
  • 樂觀鎖:適用于低競爭場景,通過版本號或 CAS 操作減少開銷。
  • 重量級鎖:適用于高競爭場景,基于系統級別的同步機制。
  • 輕量級鎖:適用于低競爭場景,通過 CAS 操作減少開銷。
  • 掛起等待鎖:適用于長時間持有鎖的場景,減少 CPU 的 使用。
  • 自旋鎖:適用于短時間持有鎖的場景,避免線程切換開銷。

二、synchronized 的優化策略

在 Java 中,synchronized 關鍵字是實現線程同步的重要工具。為了提高 synchronized 的性能,JVM 提供了多種優化策略,包括鎖優化,鎖消除,鎖粗化。這些優化策略可以顯著減少鎖的開銷,提高多線程程序的性能。

1. 鎖升級(Lazy Initialization)

1.1 定義和原理

鎖升級指 JVM 根據鎖競爭情況動態調整鎖的狀態。從偏向鎖到輕量級鎖再到重量級鎖的過程。這種機制類似于懶漢模式的思想,即在不需要時盡量做到輕量級的狀態,只有在競爭加劇的時候才轉化為重量級鎖。

  • 無鎖狀態(Unlocked):對象沒有被任何線程鎖定。
  • 偏向鎖狀態(Biased Locking):加入只有一個線程會訪問該對象,因此不需要每次都加鎖。偏向鎖通過在對象頭中加上線程 ID 來實現。
  • 輕量級鎖狀態(Lightweight Locking):當有多個線程競爭同一把鎖時,偏向鎖失效,進入輕量級鎖狀態。輕量級鎖嘗試通過 CAS(Compare And Swap)操作來獲取鎖,避免重量級鎖的開銷。
  • 重量級鎖狀態(Heavyweight Locking):當競爭進一步加劇的時候,CAS 操作失敗的次數過多,鎖會膨脹為重量級鎖,進入操作系統級別的同步機制。

1.2 鎖的狀態轉換圖

+-------------------+       +-------------------+       +-------------------+       +-------------------+
|                   |  CAS  |                   |  CAS  |                   |  Fail  |                   |
|   Unlocked        +------>+   Biased Lock     +------>+ Lightweight Lock  +------>+   Heavyweight Lock|
|                   |       |                   |       |                   |        |                   |
+-------------------+       +-------------------+       +-------------------+       +-------------------+

1.3 應用場景

鎖升級適用于各種需要同步的場景,特別是在低競爭情況下能夠顯著提高性能。

  • 如果一個鎖長時間沒有競爭,JVM 會選擇偏向鎖或輕量級鎖,減少加鎖的開銷。
  • 如果鎖競爭變得激烈,JVM 會自動將鎖升級為重量級鎖,確保線程安全。

1.4 示例代碼

public class LockUpgradeExample {private int count = 0;public synchronized void increment() {count++;}public static void main(String[] args) throws InterruptedException {LockUpgradeExample example = new LockUpgradeExample();Thread t1 = new Thread(() -> {for (int i = 0; i < 10000; i++) {example.increment();}});Thread t2 = new Thread(() -> {for (int i = 0; i < 10000; i++) {example.increment();}});t1.start();t2.start();t1.join();t2.join();System.out.println("Final count: " + example.count);}
}

2. 鎖消除(Lock Elimination)

2.1 定義和原理

鎖消除是指編譯器在運行代碼時自動識別代碼,識別出一些不必要的同步操作,將其優化掉。這可以通過逃逸分析(Escape Analysis)來實現。即分析對象是否被其他線程訪問,如果沒有,就安全的消除同步操作。

2.2 示例代碼

public class LockEliminationExample {public static void main(String[] args) {long startTime = System.currentTimeMillis(); // 當前電腦系統的時間for (int i = 0; i < 10000000; i++) {performTask();}long endTime = System.currentTimeMillis();System.out.println("Time taken: " + (endTime - startTime) + " ms");}public static void performTask() {StringBuilder sb = new StringBuilder();sb.append("Hello");sb.append("World");// sb 對象僅在當前方法內使用,不會被其他線程訪問}
}

在這個例子中,StringBuilder 只在 performTask 方法中使用,不會被其他線程訪問到,JVM 可以通過逃逸分析識別出這個對象不需要同步分析,因此進行鎖消除。

未優化前:每次創建 StringBuilder 都需要同步操作。
優化后:通過逃逸分析識別出對象不會被其他線程訪問,消除同步操作。

3. 鎖粗化(Lock Coarsening)

3.1 定義和原理

鎖粗化是指將一系列連續的細粒度的加鎖操作合并成一次粗粒度的加鎖操作,從而減少鎖的開銷。鎖粗化通常應用在循環體內存在多次加鎖操作的情況。

3.2 示例代碼

public class LockCoarseningExample {private final Object lock = new Object();private int sum = 0;public void add(int value) {synchronized (lock) {sum += value;}}public static void main(String[] args) throws InterruptedException {LockCoarseningExample example = new LockCoarseningExample();Thread t1 = new Thread(() -> {for (int i = 0; i < 10000; i++) {example.add(i);}});Thread t2 = new Thread(() -> {for (int i = 0; i < 10000; i++) {example.add(i);}});t1.start();t2.start();t1.join();t2.join();System.out.println("Final sum: " + example.sum);}
}

在這個例子中,add 方法在循環體內被多次調用,每次調用都需要進行同步操作。JVM 通過鎖粗化將多次加鎖操作合并成一次粗粒度的加鎖操作,從而減少鎖的開銷。

  • 未優化前:每次調用 add 方法都需要進行加鎖操作,導致大量的上下文切換。
  • 優化后:通過鎖粗化將多次加鎖操作合并成一次粗粒度的加鎖操作,減少上下文切換。

4. 幾種優化策略的聯系

  • 鎖升級:通過動態調整鎖的狀態,從偏向鎖到輕量級鎖再到重量級鎖,減少鎖的開銷,適用于多種同步的場景,特別是低競爭情況下能夠顯著提升性能。
  • 鎖消除:通過逃逸分析識別出不必要的同步操作,并將其優化掉。適用于那些在本地方法中創建的對象,且這些對象不會被其他線程訪問到的情況。
  • 鎖粗化:通過多次加鎖操作合成一次粗粒度的加鎖操作,減少鎖的開銷。適用于那些在循環體內存在多次加鎖的操作場景。

三、鎖策略2

1. 可重入鎖 和 不可重入鎖

1.1 定義和原理

  • 可重入鎖(Reentrant Lock):允許同一個線程多次獲取同一把鎖。每次獲取鎖時都會增加一個計數器,每次釋放鎖時都會減少計數器,只有當計數器歸零時才真正釋放鎖。

  • 不可重入鎖(Non-reentrant Lock):不允許同一個線程多次獲取同一把鎖。如果一個線程已經持有了鎖,再次嘗試獲取鎖會導致死鎖。

1.2 應用場景

  • 可重入鎖:適用于遞歸調用或需要在多個方法中使用同一把鎖的場景。

  • 不可重入鎖:適用于簡單的同步控制場景,避免了不必要的復雜性和開銷。

1.3 示例代碼

import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.Lock;public class ReentrantLockExample {private final Lock lock = new ReentrantLock();public void method1() {lock.lock();try {System.out.println("Method 1 acquired the lock.");method2(); // 遞歸調用} finally {lock.unlock();}}public void method2() {lock.lock();try {System.out.println("Method 2 acquired the lock.");} finally {lock.unlock();}}public static void main(String[] args) {ReentrantLockExample example = new ReentrantLockExample();example.method1();}
}

在這個例子中,method1 調用了 method2,由于使用的是 ReentrantLock,同一個線程可以多次獲取鎖而不會導致死鎖。

1.4 圖文并茂

+-------------------+       +-------------------+       +-------------------+
|                   |       |                   |       |                   |
|   Thread A        +-----> |   Acquire Lock    +-----> |   Enter Critical  |
|                   |       |                   |       |                   |
+-------------------+       +-------------------+       +-------------------+|                           |                          |v                           v                          v
+-------------------+       +-------------------+       +-------------------+
|                   |       |                   |       |                   |
|   Recursive Call  +------>+   Acquire Again   +------>+   Execute Code    |
|                   |       |                   |       |                   |
+-------------------+       +-------------------+       +-------------------+|                           |                          |v                           v                          v
+-------------------+       +-------------------+       +-------------------+
|                   |       |                   |       |                   |
|   Return Success  <-------+   Release Lock    <-------+   Finish Task     |
|                   |       |                   |       |                   |
+-------------------+       +-------------------+       +-------------------+
1. 初始狀態:線程 A 獲取鎖
+-------------------+       +-------------------+       +-------------------+
|                   |       |                   |       |                   |
|   Thread A        +-----> |   Acquire Lock    +-----> |   Enter Critical  |
|                   |       |                   |       |                   |
+-------------------+       +-------------------+       +-------------------+
  • Thread A:表示當前正在執行的線程。
  • Acquire Lock:線程 A 嘗試獲取鎖。由于使用的是可重入鎖(如 ReentrantLock),如果鎖沒有被其他線程持有,線程 A 可以成功獲取鎖。
  • Enter Critical:一旦成功獲取鎖,線程 A 進入臨界區(Critical Section),可以安全地訪問共享資源。
2. 遞歸調用:線程 A 再次嘗試獲取鎖
+-------------------+       +-------------------+       +-------------------+
|                   |       |                   |       |                   |
|   Recursive Call  +------>+   Acquire Again   +------>+   Execute Code    |
|                   |       |                   |       |                   |
+-------------------+       +-------------------+       +-------------------+
  • Recursive Call:線程 A 在臨界區內調用了另一個方法,而該方法也需要獲取同一把鎖。由于使用的是可重入鎖,線程 A 可以再次成功獲取鎖。
  • Acquire Again:線程 A 再次嘗試獲取鎖,由于是同一個線程,鎖的計數器增加,允許再次獲取鎖。
  • Execute Code:線程 A 繼續執行代碼邏輯,訪問共享資源。
3. 結束狀態:線程 A 釋放鎖
+-------------------+       +-------------------+       +-------------------+
|                   |       |                   |       |                   |
|   Return Success  <-------+   Release Lock    <-------+   Finish Task     |
|                   |       |                   |       |                   |
+-------------------+       +-------------------+       +-------------------+
  • Finish Task:線程 A 完成所有任務,準備退出臨界區。
  • Release Lock:線程 A 釋放鎖。每次釋放鎖時,鎖的計數器減少一次。如果計數器歸零,則真正釋放鎖,允許其他線程獲取鎖。
  • Return Success:線程 A 成功釋放鎖并返回結果。

2. 公平鎖 和 非公平鎖

2.1 定義和原理

  • 公平鎖(Fair Lock):按照請求順序獲取鎖,即先請求的線程先獲得鎖。這種機制避免了“饑餓”現象,但可能導致較高的等待時間。

  • 非公平鎖(Non-fair Lock):不保證請求順序,任何線程都有機會獲取鎖。這種機制通常具有更高的吞吐量,但可能導致某些線程長時間得不到鎖。

2.2 應用場景

  • 公平鎖:適用于對響應時間要求較高、不能容忍“饑餓”現象的場景。

  • 非公平鎖:適用于對吞吐量要求較高、可以容忍一定延遲的場景。

2.3 示例代碼

import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.Lock;public class FairAndUnfairLockExample {private static final Lock fairLock = new ReentrantLock(true); // 公平鎖private static final Lock unfairLock = new ReentrantLock(false); // 非公平鎖public static void main(String[] args) throws InterruptedException {Runnable task = () -> {try {fairLock.lock();try {System.out.println(Thread.currentThread().getName() + " acquired fair lock.");Thread.sleep(1000); // 模擬長時間持有鎖} finally {fairLock.unlock();}} catch (InterruptedException e) {e.printStackTrace();}};for (int i = 0; i < 5; i++) {Thread thread = new Thread(task, "Thread-" + i);thread.start();}Thread.sleep(6000); // 等待所有線程完成task = () -> {try {unfairLock.lock();try {System.out.println(Thread.currentThread().getName() + " acquired unfair lock.");Thread.sleep(1000); // 模擬長時間持有鎖} finally {unfairLock.unlock();}} catch (InterruptedException e) {e.printStackTrace();}};for (int i = 0; i < 5; i++) {Thread thread = new Thread(task, "Thread-" + i);thread.start();}}
}

在這個例子中,我們創建了兩個鎖實例,一個是公平鎖,另一個是非公平鎖。通過觀察輸出結果,可以看到公平鎖按照請求順序獲取鎖,而非公平鎖則不一定。

2.4 圖文并茂

+-------------------+       +-------------------+       +-------------------+
|                   |       |                   |       |                   |
|   Threads         +-----> |   Request Lock    +-----> |   Wait in Queue   |
|                   |       |                   |       |                   |
+-------------------+       +-------------------+       +-------------------+|                           |                          |v                           v                          v
+-------------------+       +-------------------+       +-------------------+
|                   |       |                   |       |                   |
|   Fair Lock       +------>+   Grant Lock      +------>+   Execute Code    |
|                   |       |                   |       |                   |
+-------------------+       +-------------------+       +-------------------+|                           |                          |v                           v                          v
+-------------------+       +-------------------+       +-------------------+
|                   |       |                   |       |                   |
|   Return Success  <-------+   Release Lock    <-------+   Finish Task     |
|                   |       |                   |       |                   |
+-------------------+       +-------------------+       +-------------------++-------------------+       +-------------------+       +-------------------+
|                   |       |                   |       |                   |
|   Threads         +-----> |   Request Lock    +-----> |   Random Order    |
|                   |       |                   |       |                   |
+-------------------+       +-------------------+       +-------------------+|                           |                          |v                           v                          v
+-------------------+       +-------------------+       +-------------------+
|                   |       |                   |       |                   |
|   Non-fair Lock   +------>+   Grant Lock      +------>+   Execute Code    |
|                   |       |                   |       |                   |
+-------------------+       +-------------------+       +-------------------+|                           |                          |v                           v                          v
+-------------------+       +-------------------+       +-------------------+
|                   |       |                   |       |                   |
|   Return Success  <-------+   Release Lock    <-------+   Finish Task     |
|                   |       |                   |       |                   |
+-------------------+       +-------------------+       +-------------------+
1. 公平鎖流程圖
+-------------------+       +-------------------+       +-------------------+
|                   |       |                   |       |                   |
|   Threads         +-----> |   Request Lock    +-----> |   Wait in Queue   |
|                   |       |                   |       |                   |
+-------------------+       +-------------------+       +-------------------+|                           |                          |v                           v                          v
+-------------------+       +-------------------+       +-------------------+
|                   |       |                   |       |                   |
|   Fair Lock       +------>+   Grant Lock      +------>+   Execute Code    |
|                   |       |                   |       |                   |
+-------------------+       +-------------------+       +-------------------+|                           |                          |v                           v                          v
+-------------------+       +-------------------+       +-------------------+
|                   |       |                   |       |                   |
|   Return Success  <-------+   Release Lock    <-------+   Finish Task     |
|                   |       |                   |       |                   |
+-------------------+       +-------------------+       +-------------------+
閱讀順序:
  1. 從左到右

    • 第一列:表示多個線程(Threads)。
    • 第二列:表示線程請求鎖(Request Lock)。
    • 第三列:表示線程進入等待隊列(Wait in Queue)。
    • 第四列:表示線程執行代碼(Execute Code)。
  2. 從上到下

    • 線程首先嘗試請求鎖(Request Lock),如果鎖已經被其他線程持有,則進入等待隊列(Wait in Queue)。
    • 當前持有鎖的線程釋放鎖后,公平鎖會按照請求順序依次授予鎖(Grant Lock),即先請求的線程先獲得鎖。
    • 獲得鎖的線程開始執行臨界區代碼(Execute Code)。
    • 執行完臨界區代碼后,線程釋放鎖(Release Lock),并返回成功(Return Success)。
2. 非公平鎖流程圖
+-------------------+       +-------------------+       +-------------------+
|                   |       |                   |       |                   |
|   Threads         +-----> |   Request Lock    +-----> |   Random Order    |
|                   |       |                   |       |                   |
+-------------------+       +-------------------+       +-------------------+|                           |                          |v                           v                          v
+-------------------+       +-------------------+       +-------------------+
|                   |       |                   |       |                   |
|   Non-fair Lock   +------>+   Grant Lock      +------>+   Execute Code    |
|                   |       |                   |       |                   |
+-------------------+       +-------------------+       +-------------------+|                           |                          |v                           v                          v
+-------------------+       +-------------------+       +-------------------+
|                   |       |                   |       |                   |
|   Return Success  <-------+   Release Lock    <-------+   Finish Task     |
|                   |       |                   |       |                   |
+-------------------+       +-------------------+       +-------------------+
閱讀順序:
  1. 從左到右

    • 第一列:表示多個線程(Threads)。
    • 第二列:表示線程請求鎖(Request Lock)。
    • 第三列:表示線程以隨機順序等待(Random Order)。
    • 第四列:表示線程執行代碼(Execute Code)。
  2. 從上到下

    • 線程首先嘗試請求鎖(Request Lock),如果鎖已經被其他線程持有,則進入等待狀態(Random Order),但不保證按請求順序排隊。
    • 當前持有鎖的線程釋放鎖后,非公平鎖可能會優先授予剛剛釋放鎖的線程或其他正在請求鎖的線程(Grant Lock),而不一定按照請求順序。
    • 獲得鎖的線程開始執行臨界區代碼(Execute Code)。
    • 執行完臨界區代碼后,線程釋放鎖(Release Lock),并返回成功(Return Success)。

3. 總結

  • 可重入鎖 vs 不可重入鎖

    • 可重入鎖:允許同一個線程多次獲取同一把鎖,適用于遞歸調用或需要在多個方法中使用同一把鎖的場景。
    • 不可重入鎖:不允許同一個線程多次獲取同一把鎖,適用于簡單的同步控制場景。
  • 公平鎖 vs 非公平鎖

    • 公平鎖:按照請求順序獲取鎖,適用于對響應時間要求較高、不能容忍“饑餓”現象的場景。
    • 非公平鎖:不保證請求順序,任何線程都有機會獲取鎖,適用于對吞吐量要求較高、可以容忍一定延遲的場景。

四、CAS(Compare-And-Swap)問題

1. 通過偽代碼逐步引出 CAS 問題

我們將從一個簡單的場景開始,逐步引出 CAS 操作的概念和其潛在的問題。

偽代碼:

boolean CAS(address, expectValue, swapValue) {if (&address == expectValue) {&address = swapValue;return true;}return false;
}

1.1 簡單變量更新

假設我們有一個共享的變量 value ,多個線程要對其進行更新操作。最直接的方法是對其進行加鎖操作保證線程安全。

public class SimpleUpdate {private int value = 0;public synchronized void update(int newValue) {value = newValue;}public static void main(String[] args) throws InterruptedException {SimpleUpdate updater = new SimpleUpdate();Thread t1 = new Thread(() -> updater.update(1));Thread t2 = new Thread(() -> updater.update(2));t1.start();t2.start();t1.join();t2.join();System.out.println("Final Value: " + updater.value);}
}

在這個例子中,我們使用 synchronized 關鍵字來確保只有一個線程對共享變量 value 進行更新操作,從而避免競爭條件。

1.2 引入無鎖更新的想法

雖然鎖機制可以確保線程安全,但它也有一定的開銷,特別在高并發環境下。因此,我們可以考慮一種無鎖的更新方法。下面是最初的嘗試:

public class NaiveUpdate {private int value = 0;public void update(int newValue) {// 直接更新值value = newValue;}public static void main(String[] args) throws InterruptedException {NaiveUpdate updater = new NaiveUpdate();Thread t1 = new Thread(() -> updater.update(1));Thread t2 = new Thread(() -> updater.update(2));t1.start();t2.start();t1.join();t2.join();System.out.println("Final Value: " + updater.value);}
}

這種方法存在明顯的競爭條件問題。兩個線程可能同時讀取和更新 value ,導致結果不可預測。

1.3 引入比較-交換的思想

為了確保線程安全,我們需要在更新之前檢查當前值(value)是否與預期值(expectedValue)一致。如果一致,就進行更新操作,如果不一致,就不做任何處理。這正是 CAS 操作的核心思想。

public class CompareAndSwapUpdate {private int value = 0;public boolean compareAndSwap(int expectedValue, int swapValue) {if (value == expectedValue) {value = swapValue;return true;}return false;}public static void main(String[] args) throws InterruptedException {CompareAndSwapUpdate updater = new CompareAndSwapUpdate();Thread t1 = new Thread(() -> {while (!updater.compareAndSwap(0, 1)) {// 自旋等待}});Thread t2 = new Thread(() -> {while (!updater.compareAndSwap(0, 2)) {// 自旋等待}});t1.start();t2.start();t1.join();t2.join();System.out.println("Final Value: " + updater.value);}
}

在這個例子中,定義了一個 compareAndSwap 方法,首先檢查 value 是否和 expectedValue 相等。如果相等,就將 value 更新為 swapValue ,并返回 true 。如果不相等,不進行任何操作,并返回 false 。每個線程都在不斷地嘗試更新,直到成功為止。

1.4 引出 CAS 操作的偽代碼

回到最初的偽代碼:

boolean CAS(address, expectValue, swapValue) {if (&address == expectValue) {&address = swapValue;return true;}return false;
}

在這段偽代碼中:

  • address :要修改的內存地址
  • expectValue :當前預期的值。
  • swapValue :要更新的新值。
  • 如果 &address 的值等于 expectValue,則將其更新為 swapValue 并返回 true;否則返回 false

1.5 討論 CAS 操作的基本特性

  • 原子性:CAS 操作是一種原子操作,意味著它在硬件層面上是不可分割的,不會被其他線程打斷。
  • 無鎖編程:CAS 操作不需要顯示的鎖,因此可以避免死鎖的開銷和上下文切換。
  • 高效性:在低競爭的情況下,CAS 操作比鎖機制更加高效,因為它直接在硬件級別上進行原子操作。

1.6 總結 CAS 操作的應用場景

  1. 計數器:例如 AtomicInteger 類中的自增或自減。
  2. 緩存:用于更新緩存中的數據。
  3. 隊列:用于實現無鎖隊列等數據結構。

2. 通過 CAS 操作實現原子類

我們將通過一段偽代碼逐步引出如何使用 CAS 操作來實現簡單的原子類,并詳細分析其工作原理和應用場景。

偽代碼:

class AtomicInteger { private int value; public int getAndIncrement() { int oldValue = value; while ( CAS(value, oldValue, oldValue + 1) != true) { oldValue = value; } return oldValue; }
}

2.1 定義問題

假設我們需要實現一個線程安全的計數器,支持自增操作,最直接的辦法是使用鎖機制:

public class SimpleCounter {private int value = 0;public synchronized int getAndIncrement() {return value++;}public static void main(String[] args) throws InterruptedException {SimpleCounter counter = new SimpleCounter();Thread t1 = new Thread(() -> {for (int i = 0; i < 1000; i++) {counter.getAndIncrement();}});Thread t2 = new Thread(() -> {for (int i = 0; i < 1000; i++) {counter.getAndIncrement();}});t1.start();t2.start();t1.join();t2.join();System.out.println("Final Value: " + counter.value);}
}

在這個例子中,使用 synchronized 關鍵字確保只有一個線程能夠訪問到 getAndIncrement 方法,從而避免競爭條件。然而,這種方式需要一定的開銷,特別是在高并發環境下。

2.2 引入無鎖更新的想法

為了提高性能,我們考慮使用一種無鎖的更新機制。CAS(Compare-And-Swap)操作可以實現這一點。下面是我們的初步嘗試:

public class NaiveAtomicInteger {private int value = 0;public boolean compareAndSwap(int expectedValue, int newValue) {if (value == expectedValue) {value = newValue;return true;}return false;}public static void main(String[] args) throws InterruptedException {NaiveAtomicInteger atomicInteger = new NaiveAtomicInteger();Thread t1 = new Thread(() -> {int oldValue = atomicInteger.value;while (!atomicInteger.compareAndSwap(oldValue, oldValue + 1)) {oldValue = atomicInteger.value;}});Thread t2 = new Thread(() -> {int oldValue = atomicInteger.value;while (!atomicInteger.compareAndSwap(oldValue, oldValue + 1)) {oldValue = atomicInteger.value;}});t1.start();t2.start();t1.join();t2.join();System.out.println("Final Value: " + atomicInteger.value);}
}

在這個例子中,我們定義了一個 compareAndSwap 方法,它首先檢查 value 是否等于 expectedValue,如果是,則將其更新為 newValue 并返回 true;否則返回 false。每個線程在一個循環中不斷嘗試更新,直到成功為止。

2.3 引出 CAS 操作的偽代碼

回到最初的偽代碼

class AtomicInteger { private int value; public int getAndIncrement() { int oldValue = value; while ( CAS(value, oldValue, oldValue + 1) != true) { oldValue = value; } return oldValue; }
}

在這段偽代碼中:

  • value :是要修改的共享變量。
  • oldValue :是當前讀取到的變量。
  • CAS(value, oldValue, oldValue + 1) :這是一個原子操作。第一步,先判斷 oldValuevalue 是否相等。假如相等,就將其更新為 oldValue + 1 ,并返回 true;否則,返回 false

2.4 實現真實的 CAS 操作

在 Java 中,可以通過 sun.misc.Unsafe 類或 java.util.concurrent.atomic 包中的類來實現。下面是使用 AtomicInteger 實現的完整示例:

import java.util.concurrent.atomic.AtomicInteger;public class AtomicCounter {private final AtomicInteger value = new AtomicInteger(0);public int getAndIncrement() {return value.getAndIncrement();}public static void main(String[] args) throws InterruptedException {AtomicCounter counter = new AtomicCounter();Thread t1 = new Thread(() -> {for (int i = 0; i < 1000; i++) {counter.getAndIncrement();}});Thread t2 = new Thread(() -> {for (int i = 0; i < 1000; i++) {counter.getAndIncrement();}});t1.start();t2.start();t1.join();t2.join();System.out.println("Final Value: " + counter.value.get());}
}

在這個例子中,我們使用 AtomicInteger 類來實現一個線程安全的計數器。AtomicInteger 類內部實現了 CAS 操作,確保多個線程可以安全的進行自增操作。

2.5 分析代碼的工作原理

詳細分析一下 getAndIncrement 方法的工作原理:

  • 讀取當前值:首先讀取 value 的值,并保存到 oldValue 中。
  • CAS 操作:嘗試將 value 更新為 oldValue + 1 。如果 value 等于 oldValue,更新成功就返回 true;否則就返回 false
  • 重試機制:如果 CAS 操作失敗(value 值在此期間被其他線程修改),則重新讀取 value 并再次嘗試 CAS 操作,直到成功為止。
  • 返回舊值:成功之后,返回最初的值 oldValue ,即使在此期間 value 被多次修改。

2.6 圖文并茂說明

  • 圖解 CAS 操作流程
+-------------------+       +-------------------+       +-------------------+
|                   |       |                   |       |                   |
|   Current Value   +-----> |   Expected Value  +-----> |   New Value       |
|                   |       |                   |       |                   |
+-------------------+       +-------------------+       +-------------------+|                           |                          |v                           v                          v
+-------------------+       +-------------------+       +-------------------+
|                   |       |                   |       |                   |
|   Compare         +------>+   If Equal        +------>+   Set New Value   |
|                   |       |                   |       |                   |
+-------------------+       +-------------------+       +-------------------+|                           |                          |v                           v                          v
+-------------------+       +-------------------+       +-------------------+
|                   |       |                   |       |                   |
|   Return True     <-------+   Update Success  <-------+   Return False    |
|                   |       |                   |       |                   |
+-------------------+       +-------------------+       +-------------------+
  • 自旋等待機制
+-------------------+       +-------------------+       +-------------------+
|                   |       |                   |       |                   |
|   Read Value      +-----> |   CAS Operation    +-----> |   Retry if Failed|
|                   |       |                   |       |                   |
+-------------------+       +-------------------+       +-------------------+|                           |                          |v                           v                          v
+-------------------+       +-------------------+       +-------------------+
|                   |       |                   |       |                   |
|   Return Old      <-------+   Update Success  <-------+   Read Again      |
|                   |       |                   |       |                   |
+-------------------+       +-------------------+       +-------------------+

2.7 總結

通過上述步驟,我們逐步引出了如何使用 CAS 操作來實現一個簡單的原子類,并詳細分析了其工作原理和應用場景。CAS 操作的核心思想是通過比較和交換實現無鎖編程,確保多線程環境下的安全性。盡管 CAS 操作有一些潛在的問題(如 ABA 問題和自旋開銷),但在低競爭情況下,它通常比鎖機制更高效。

3. 通過 CAS 操作實現自旋鎖

我們將通過一段偽代碼逐步引出自旋鎖的概念,并詳細分析其工作原理和應用場景。

偽代碼:

public class SpinLock {private Thread owner = null;public void lock() {// 通過 CAS 看當前鎖是否被某個線程持有。// 如果這個鎖已經被別的線程持有,那么就自旋等待。// 如果這個鎖沒有被別的線程持有,那么就把 owner 設置為當前嘗試加鎖的線程。while (!CAS(this.owner, null, Thread.currentThread())) {}}public void unlock() {this.owner = null;}
}

3.1 定義問題

假設我們需要一個簡單的互斥鎖(Mutex),確保只有一個線程在同一時間內訪問共享資源。最直接的方法是使用顯性的鎖機制。

public class SimpleLock {private boolean isLocked = false;public synchronized void lock() {while (isLocked) {try {wait();} catch (InterruptedException e) {Thread.currentThread().interrupt();}}isLocked = true;}public synchronized void unlock() {isLocked = false;notifyAll();}public static void main(String[] args) throws InterruptedException {SimpleLock lock = new SimpleLock();Thread t1 = new Thread(() -> {lock.lock();try {System.out.println("Thread 1 acquired the lock.");Thread.sleep(1000); // 模擬長時間持有鎖} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();System.out.println("Thread 1 released the lock.");}});Thread t2 = new Thread(() -> {lock.lock();try {System.out.println("Thread 2 acquired the lock.");} finally {lock.unlock();System.out.println("Thread 2 released the lock.");}});t1.start();t2.start();t1.join();t2.join();}
}

在這個例子中,通過 synchronized 關鍵字來實現只有一個線程在一個時間段內訪問到共享資源,在鎖被釋放是喚醒等待的線程,但是這種方法存在一定的開銷,尤其在高并發的環境下。

3.2 引入無鎖更新的想法

為了提高性能,我們可以考慮使用一種無鎖的更新方法。CAS(Compare-And-Swap)操作可以幫助我們實現這一點。下面是我們的初步嘗試:

public class NaiveSpinLock {private Thread owner = null;public void lock() {Thread currentThread = Thread.currentThread();while (owner != null && owner != currentThread) {// 自旋等待}owner = currentThread;}public void unlock() {owner = null;}public static void main(String[] args) throws InterruptedException {NaiveSpinLock spinLock = new NaiveSpinLock();Thread t1 = new Thread(() -> {spinLock.lock();try {System.out.println(Thread.currentThread().getName() + " acquired the lock.");Thread.sleep(1000); // 模擬長時間持有鎖} catch (InterruptedException e) {e.printStackTrace();} finally {spinLock.unlock();System.out.println(Thread.currentThread().getName() + " released the lock.");}});Thread t2 = new Thread(() -> {spinLock.lock();try {System.out.println(Thread.currentThread().getName() + " acquired the lock.");} finally {spinLock.unlock();System.out.println(Thread.currentThread().getName() + " released the lock.");}});t1.start();Thread.sleep(100); // 確保 t1 先開始t2.start();t1.join();t2.join();}
}

在這個例子中,我們定義了一個 lock 方法,它首先檢查當前鎖是否被其他線程持有( owner != null ),并且判斷當前線程是否已經是獲取到鎖對象的線程(owner != currentThread)。如果是,就將 owner 設置成當前線程;否則進入自旋等待狀態。每個線程在一個循環中不斷地嘗試獲取鎖,直到成功為止。

3.3 引出 CAS 操作的偽代碼

現在讓我們回到最初提供的偽代碼,并解釋它的含義:

  • owner :表示當前持有鎖的線程。
  • CAS(this.owner, null, Thread.currentThread()) :是一個原子操作,它首先檢查 owner 是否為 null,檢查鎖是否已經被其他線程持有。如果是,則將其更新為當前線程并返回 true;否則返回 false

3.4 實現真實的 CAS 操作

在 Java 中,CAS 操作可以通過 sun.misc.Unsafe 類或 java.util.concurrent.atomic 包中的類來實現。下面是一個使用 AtomicReference 實現的完整示例:

import java.util.concurrent.atomic.AtomicReference;public class SpinLock {private final AtomicReference<Thread> owner = new AtomicReference<>();public void lock() {Thread currentThread = Thread.currentThread();// 自旋等待,直到成功獲取鎖while (!owner.compareAndSet(null, currentThread)) {// 可以在這里添加一些優化,例如短暫休眠以減少 CPU 開銷// Thread.yield(); 或者 LockSupport.parkNanos(1L);}}public void unlock() {Thread currentThread = Thread.currentThread();if (owner.get() == currentThread) {owner.set(null);} else {throw new IllegalMonitorStateException("Calling thread does not hold the lock");}}public static void main(String[] args) throws InterruptedException {SpinLock spinLock = new SpinLock();Thread t1 = new Thread(() -> {spinLock.lock();try {System.out.println(Thread.currentThread().getName() + " acquired the lock.");Thread.sleep(1000); // 模擬長時間持有鎖} catch (InterruptedException e) {e.printStackTrace();} finally {spinLock.unlock();System.out.println(Thread.currentThread().getName() + " released the lock.");}});Thread t2 = new Thread(() -> {spinLock.lock();try {System.out.println(Thread.currentThread().getName() + " acquired the lock.");} finally {spinLock.unlock();System.out.println(Thread.currentThread().getName() + " released the lock.");}});t1.start();Thread.sleep(100); // 確保 t1 先開始t2.start();t1.join();t2.join();}
}

在這個例子中,使用 AtomicReference 類來實現自旋鎖。AtomicReference 內部實現了 CAS 操作,確保多個線程能夠安全地進行鎖操作。

3.5 分析代碼的工作原理

詳細分析一下 lockunlock 方法的工作原理。

  • 讀取當前值:首先讀取 owner 的值。
  • CAS 操作:嘗試將 owner 更新為當前線程,如果當前的 ownernull,則更新成功并返回 true;否則返回 false
  • 重試機制:如果 CAS 操作失敗(即 owner 在此期間被其他線程修改),則重新讀取當前的 owner 并再次嘗試 CAS 操作,直到成功為止。
  • 解除操作:當線程完成任務后,調用 unlock 方法將 owner 設置為 null,允許其他線程獲取鎖。

3.6 圖文并茂說明

  • 圖解自旋鎖流程
+-------------------+       +-------------------+       +-------------------+
|                   |       |                   |       |                   |
|   Current Owner   +-----> |   Expected Owner  +-----> |   New Owner       |
|                   |       |                   |       |                   |
+-------------------+       +-------------------+       +-------------------+|                           |                          |v                           v                          v
+-------------------+       +-------------------+       +-------------------+
|                   |       |                   |       |                   |
|   Compare         +------>+   If Equal        +------>+   Set New Owner   |
|                   |       |                   |       |                   |
+-------------------+       +-------------------+       +-------------------+|                           |                          |v                           v                          v
+-------------------+       +-------------------+       +-------------------+
|                   |       |                   |       |                   |
|   Return True     <-------+   Update Success  <-------+   Return False    |
|                   |       |                   |       |                   |
+-------------------+       +-------------------+       +-------------------+
  • 自旋等待機制
+-------------------+       +-------------------+       +-------------------+
|                   |       |                   |       |                   |
|   Read Owner      +-----> |   CAS Operation    +-----> |   Retry if Failed|
|                   |       |                   |       |                   |
+-------------------+       +-------------------+       +-------------------+|                           |                          |v                           v                          v
+-------------------+       +-------------------+       +-------------------+
|                   |       |                   |       |                   |
|   Return Old      <-------+   Update Success  <-------+   Read Again      |
|                   |       |                   |       |                   |
+-------------------+       +-------------------+       +-------------------+

3.7 總結

通過上述步驟,我們逐步引出了如何使用 CAS 操作來實現一個簡單的自旋鎖,并詳細分析了其工作原理和應用場景。自旋鎖的核心思想是通過不斷嘗試獲取鎖,適用于短時間持有鎖的場景,因為它避免了線程上下文切換的開銷。盡管自旋鎖有一些潛在的問題(如高 CPU 開銷),但在低競爭情況下,它通常比鎖機制更高效。

4. CAS 中 ABA 問題

4.1 詳細介紹 CAS 中的 ABA 問題

什么是 ABA 問題?

ABA 問題 (Atomicity, Consistency, and Availability)是指由于某個線程在讀取值后被其他線程修改兩次,最終又變回原來的值導致的問題。具體說明如下:

  1. 線程 A 讀取 V 的值為 A
  2. 線程 B 修改 V 的值為 B,然后將 V 的值改回 A
  3. 當線程 A 檢查 V 的值時,發現還是 A,認為變量沒有被修改過。

盡管變量 V 的值看起來沒有發生變化,但實際上是被修改過的,這可能會導致邏輯錯誤。

4.2 輔助代碼示例

下面是一個簡單的 Java 示例,展示了 ABA 問題的發生過程:

import java.util.concurrent.atomic.AtomicInteger;public class ABADemo {private static AtomicInteger value = new AtomicInteger(0);public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {// 線程 A 嘗試將 value 自增 1int expectedValue = value.get();while (!value.compareAndSet(expectedValue, expectedValue + 1)) {expectedValue = value.get();}System.out.println("Thread A: Value updated to " + value.get());});Thread t2 = new Thread(() -> {// 線程 B 先將 value 設為 1,再設回 0value.set(1);value.set(0);System.out.println("Thread B: Value set to 1 then back to 0");});t1.start();Thread.sleep(100); // 確保 t1 先開始t2.start();t1.join();t2.join();System.out.println("Final Value: " + value.get());}
}

在這個例子中,線程 A 通過 CAS 操作將 value 自增 1,而線程 B 則先將 value 設為 1,然后再設回 0。由于線程 A 在檢查 value 值的值時,發現它還是 0 ,就繼續進行自增操作。但實際上,value 值已經被修改過一次了,但還是識別不出來,這就是 ABA 問題。

4.3 圖文并茂理解 ABA 問題

  1. ABA 問題的流程圖
+-------------------+       +-------------------+       +-------------------+
|                   |       |                   |       |                   |
|   Initial Value   +-----> |   Thread B Modify +-----> |   Thread B Revert |
|                   |       |                   |       |                   |
+-------------------+       +-------------------+       +-------------------+|                           |                          |v                           v                          v
+-------------------+       +-------------------+       +-------------------+
|                   |       |                   |       |                   |
|   Read by Thread A+------>+   Check Value     +------>+   Update Failed  |
|                   |       |                   |       |                   |
+-------------------+       +-------------------+       +-------------------+|                           |                          |v                           v                          v
+-------------------+       +-------------------+       +-------------------+
|                   |       |                   |       |                   |
|   Return Old      <-------+   Update Success  <-------+   Read Again      |
|                   |       |                   |       |                   |
+-------------------+       +-------------------+       +-------------------+
  1. ABA 問題的具體步驟
    1. 初始化:假設初始值為 value
    2. 線程 A
    • 讀取 value 值,保存到 expectValue 中。
    • 嘗試執行 CAS(value, 0, 1) 操作,如果成功,將 value 更新成 1 ,如果失敗,則返回 expectedValue 為 0 。
    1. 線程 B:先將 value 設置為 1 ;再將 value 設回 0 。
    2. 線程 A 繼續嘗試
    • 讀取 value 值,保存到 expectValue 中。
    • 再次嘗試 CAS(value, 0, 1) 操作,因為當前 value 值為 0,因此操作成功,將 value 值更新為 1 。
    1. 結果:由于 value 值被修改過一次,但線程 A 還是無法察覺這一變化,繼續進行自增操作。

4.4 解決 ABA 問題的方法

為了防止 ABA 問題的發生,可以使用帶有版本號或時間戳的 CAS 操作。Java 提供了 AtomicStampedReference 類來解決這個問題。

  1. 使用 AtomicStampedReference 解決 ABA 問題
import java.util.concurrent.atomic.AtomicStampedReference;public class ABASolution {private static AtomicStampedReference<Integer> value = new AtomicStampedReference<>(0, 0);public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {int[] stampHolder = new int[1];Integer currentValue = value.get(stampHolder);int currentStamp = stampHolder[0];boolean success = value.compareAndSet(currentValue, currentValue + 1, currentStamp, currentStamp + 1);System.out.println("Thread 1: " + success + ", New Value: " + value.getReference());});Thread t2 = new Thread(() -> {int[] stampHolder = new int[1];Integer currentValue = value.get(stampHolder);int currentStamp = stampHolder[0];value.compareAndSet(currentValue, currentValue + 1, currentStamp, currentStamp + 1);value.compareAndSet(currentValue + 1, currentValue, currentStamp + 1, currentStamp + 2);System.out.println("Thread 2: Value set to " + (currentValue + 1) + " then back to " + currentValue);});t1.start();Thread.sleep(100); // 確保 t1 先開始t2.start();t1.join();t2.join();System.out.println("Final Value: " + value.getReference());}
}

在這個例子中,我們使用 AtomicStampedReference 類來跟蹤變量的版本號(或時間戳)。每次修改變量,都會更新版本號。這樣,即使變量的值變回原來的狀態,版本號也會不一樣,就從而避免了 ABA 問題。

  1. AtomicStampedReference 的工作原理

AtomicStampedReference 使用一個整數作為版本號,與引用一起存儲。每次進行 CAS 操作時,不僅比較引用的值,還比較版本號。只有當引用的值和版本號都匹配時,才允許更新。

public boolean compareAndSet(V expectedReference,V newReference,int expectedStamp,int newStamp) {Pair<V> current = pair;returnexpectedReference == current.reference &&expectedStamp == current.stamp &&((newReference == current.reference &&newStamp == current.stamp) ||casPair(current, Pair.of(newReference, newStamp)));
}

4.5 總結

  • ABA 問題:由于某個線程在讀取值后,被其他線程修改兩次,最終又變回原來的值導致的問題。
  • CAS 操作中的 ABA 問題:CAS 操作只是讓當前的值和預期的值進行比較,而沒有考慮到中間是否會發生變化,這樣檢查不出 ABA 問題。
  • 解決方案:使用帶版本號或時間戳的 CAS 操作(AtomicStampedReference),即使變量變回原來的狀態,版本號不同,從而避免 ABA 問題。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/67158.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/67158.shtml
英文地址,請注明出處:http://en.pswp.cn/web/67158.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

董事會辦公管理系統的需求設計和實現

該作者的原創文章目錄&#xff1a; 生產制造執行MES系統的需求設計和實現 企業后勤管理系統的需求設計和實現 行政辦公管理系統的需求設計和實現 人力資源管理HR系統的需求設計和實現 企業財務管理系統的需求設計和實現 董事會辦公管理系統的需求設計和實現 公司組織架構…

pytest自動化測試 - pytest夾具的基本概念

<< 返回目錄 1 pytest自動化測試 - pytest夾具的基本概念 夾具可以為測試用例提供資源(測試數據)、執行預置條件、執行后置條件&#xff0c;夾具可以是函數、類或模塊&#xff0c;使用pytest.fixture裝飾器進行標記。 1.1 夾具的作用范圍 夾具的作用范圍&#xff1a; …

esp32-C3 實現DHT11(溫濕度)

安裝DHT傳感器庫&#xff1a; 在Arduino IDE中&#xff0c;進入項目 > 加載庫 > 管理庫。搜索DHT sensor library并安裝。 編寫代碼 定義引腳和傳感器類型初始化傳感器判斷傳感器是否正常讀取數據 源碼 #include <DHT.h> #include <DHT_U.h>// 定義DHT傳感器…

java構建工具之Gradle

自定義任務 任務定義方式&#xff0c;總體分為兩大類:一種是通過 Project 中的task()方法,另一種是通過tasks 對象的 create 或者register 方法。 //任務名稱,閉包都作為參數println "taskA..." task(A,{ }) //閉包作為最后一個參數可以直接從括號中拿出來println …

【Pytest】生成html報告中,中文亂碼問題解決方案

import pytestif __name__ "__main__":# 只運行 tests 目錄下的測試用例&#xff0c;并生成 HTML 報告pytest.main([-v, -s, --htmlreport.html, tests])可以以上方式生成&#xff0c;也可以在pytest.ini中設置 [pytest] addopts --htmlreport.html --self-contai…

MyBatis最佳實踐:提升數據庫交互效率的秘密武器

第一章&#xff1a;框架的概述&#xff1a; MyBatis 框架的概述&#xff1a; MyBatis 是一個優秀的基于 Java 的持久框架&#xff0c;內部對 JDBC 做了封裝&#xff0c;使開發者只需要關注 SQL 語句&#xff0c;而不關注 JDBC 的代碼&#xff0c;使開發變得更加的簡單MyBatis 通…

《Java程序設計》課程考核試卷

一、單項選擇題&#xff08;本大題共10個小題&#xff0c;每小題2分&#xff0c;共20分&#xff09; 1.下列用來編譯Java源文件為字節碼文件的工具是&#xff08; &#xff09;。 A.java B.javadoc C.jar D.javac 2…

【25考研】人大計算機考研復試該怎么準備?有哪些注意事項?

人大畢竟是老牌985&#xff0c;復試難度不會太低&#xff01;建議同學認真復習&#xff01;沒有機試還是輕松一些的&#xff01; 一、復試內容 由公告可見&#xff0c;復試包含筆試及面試&#xff0c;沒有機試&#xff01; 二、參考書目 官方無給出參考書目&#xff0c;可參照…

vue3中Teleport的用法以及使用場景

1. 基本概念 Teleport 是 Vue3 提供的一個內置組件&#xff0c;它可以將組件的內容傳送到 DOM 樹的任何位置&#xff0c;而不受組件層級的限制。這在處理模態框、通知、彈出菜單等需要突破組件層級限制的場景中特別有用。 1.1 基本語法 <template><teleport to&quo…

使用openwrt搭建ipsec隧道

背景&#xff1a;最近同事遇到了個ipsec問題&#xff0c;做的ipsec特性&#xff0c;ftp下載ipv6性能只有100kb, 正面定位該問題也蠻久了&#xff0c;項目沒有用openwrt, 不過用了開源組件strongswan, 加密算法這些也是內核自帶的&#xff0c;想著開源的不太可能有問題&#xff…

基于AnolisOS 8.6安裝GmSSL 3.1.1及easy_gmssl庫測試國密算法

測試環境 Virtual Box&#xff0c;AnolisOS-8.6-x86_64-minimal.iso&#xff0c;4 vCPU, 8G RAM, 60 vDisk。最小化安裝。需聯網。 系統環境 關閉防火墻 systemctl stop firewalld systemctl disable firewalld systemctl status firewalld selinux關閉 cat /etc/selinux/co…

HTML從入門到精通:鏈接與圖像標簽全解析

系列文章目錄 01-從零開始學 HTML&#xff1a;構建網頁的基本框架與技巧 02-HTML常見文本標簽解析&#xff1a;從基礎到進階的全面指南 03-HTML從入門到精通&#xff1a;鏈接與圖像標簽全解析 文章目錄 系列文章目錄前言一、鏈接與圖像標簽&#xff08;HTML 標簽基礎&#xff…

[STM32 - 野火] - - - 固件庫學習筆記 - - -十一.電源管理系統

一、電源管理系統簡介 電源管理系統是STM32硬件設計和系統運行的基礎&#xff0c;它不僅為芯片本身提供穩定的電源&#xff0c;還通過多種電源管理功能優化功耗、延長電池壽命&#xff0c;并確保系統的可靠性和穩定性。 二、電源監控器 作用&#xff1a;保證STM32芯片工作在…

數字圖像處理:實驗六

uu們&#xff01;大家好&#xff0c;2025年的新年就要到來&#xff0c;咸魚哥在這里祝大家在2025年每天開心快樂&#xff0c;天天掙大錢&#xff0c;自由自在&#xff0c;健健康康&#xff0c;萬事如意&#xff01;&#xff08;要是咸魚哥嘴笨的話&#xff0c;還望大家多多包涵…

Langchain+文心一言調用

import osfrom langchain_community.llms import QianfanLLMEndpointos.environ["QIANFAN_AK"] "" os.environ["QIANFAN_SK"] ""llm_wenxin QianfanLLMEndpoint()res llm_wenxin.invoke("中國國慶日是哪一天?") print(…

上海亞商投顧:滬指沖高回落 大金融板塊全天強勢 上海亞商投

上海亞商投顧前言&#xff1a;無懼大盤漲跌&#xff0c;解密龍虎榜資金&#xff0c;跟蹤一線游資和機構資金動向&#xff0c;識別短期熱點和強勢個股。 一&#xff0e;市場情緒 市場全天沖高回落&#xff0c;深成指、創業板指午后翻綠。大金融板塊全天強勢&#xff0c;天茂集團…

農產品價格報告爬蟲使用說明

農產品價格報告爬蟲使用說明 # ************************************************************************** # * * # * 農產品價格報告爬蟲 …

3.4 Go函數作用域(標識符)

作用域標識符 簡單來說&#xff0c;作用域指的是標識符可以起作用的范圍&#xff0c;即其可見范圍。將標識符的可見性限制在一定范圍內&#xff0c;這個范圍就是作用域。 把標識符約束在一定的可見范圍內&#xff0c;這個范圍就是作用域。 1. 宇宙塊 特點&#xff1a;預定義…

kaggle比賽入門 - House Prices - Advanced Regression Techniques(第二部分)

本文承接上一篇 1. 分析住宅類型&#xff08;BldgType&#xff09;的分布以及它們與銷售價格&#xff08;SalePrice&#xff09;的關系 # 1. distribution of dwelling types and their relation to sale prices # BldgType: Type of dwellingdwelling_types df[BldgType].v…

使用shell命令安裝virtualbox的虛擬機并導出到vagrant的Box

0. 安裝virtualbox and vagrant [rootolx79vagrant ~]# cat /etc/resolv.conf #search 114.114.114.114 nameserver 180.76.76.76-- install VirtualBox yum install oraclelinux-developer-release-* wget https://yum.oracle.com/RPM-GPG-KEY-oracle-ol7 -O /etc/pki/rpm-g…