动态

详情 返回 返回

Volatile不保證原子性及解決方案 - 动态 详情

原子性的意義

原子性特別是在併發編程領域,是一個極其重要的概念,原子性指的是一個操作或一組操作要麼全部執行成功,要麼全部不執行,不會出現部分執行的情況。這意味着原子性操作是不可分割的,它們在執行過程中不會被其他操作中斷或干擾。

原子性的意義在於它保證了數據的一致性和程序的正確性。在多線程或多進程的環境中,當多個操作同時訪問和修改共享數據時,如果沒有原子性保證,可能會導致數據不一致或不確定的結果。例如,如果一個線程在讀取某個數據時,另一個線程同時修改了這個數據,那麼第一個線程讀取到的數據可能是不正確的。通過確保操作的原子性,可以避免這種情況,從而維護數據的完整性和程序的正確執行。

瞭解了上面的原子性的重要概念後,接下來一起聊一聊 volatile 關鍵字。

volatile 關鍵字在 Java 中用於確保變量的更新對所有線程都是可見的,但它並不保證複合操作的原子性。這意味着當多個線程同時訪問一個 volatile 變量時,可能會遇到讀取不一致的問題,儘管它們不會看到部分更新的值。

Volatile 的限制

  • 不保證原子性:volatile 變量的單個讀寫操作是原子的,但複合操作(如自增或同步塊)不是原子的。
  • 不保證順序性:volatile 變量的讀寫操作不會與其他操作(如非 volatile 變量的讀寫)發生重排序。

一個例子

用一個示例來解釋會更清楚點,假如我們有一段代碼是這樣的:

class Counter {
    private volatile int count = 0;

    void increment() {
        count++;
    }

    int getCount() {
        return count;
    }
}

儘管 count 是 volatile 變量,但 increment 方法中的複合操作 count++(讀取-增加-寫入)不是原子的。因此,在多線程環境中,多個線程可能會同時讀取相同的初始值,然後增加它,導致最終值低於預期。

volatile 不保證原子性的代碼驗證

以下是一個簡單的 Java 程序,演示了 volatile 變量在多線程環境中不保證複合操作原子性的問題:


public class VolatileTest {
    private static volatile int counter = 0;

    public static void main(String[] args) throws InterruptedException {
        int numberOfThreads = 10000;
        Thread[] threads = new Thread[numberOfThreads];

        for (int i = 0; i < numberOfThreads; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < 100; j++) {
                    counter++;
                }
            });
            threads[i].start();
        }

        for (int i = 0; i < numberOfThreads; i++) {
            threads[i].join();
        }

        System.out.println("Expected count: " + (numberOfThreads * 100));
        System.out.println("Actual count: " + counter);
    }
}

在這個例子中:

  • counter 是一個 volatile 變量。
  • 每個線程都會對 counter 執行 100 次自增操作。
  • 理論上,如果 counter++ 是原子的,最終的 counter 值應該是 10000 * 100。

然而,由於 counter++ 包含三個操作:讀取 counter 的值、增加 1、寫回 counter 的值,這些操作不是原子的。因此,在多線程環境中,最終的 counter 值通常會小於預期值,這證明了 volatile 變量不保證複合操作的原子性。

解決方案

1. 使用 synchronized 方法或塊:

  • 將訪問 volatile 變量的方法或代碼塊聲明為 synchronized,確保原子性和可見性。
class Counter {
    private volatile int count = 0;

    synchronized void increment() {
        count++;
    }

    synchronized int getCount() {
        return count;
    }
}

2. 使用 AtomicInteger 類:

java.util.concurrent.atomic 包中的 AtomicInteger 提供了原子操作,可以替代 volatile 變量。


import java.util.concurrent.atomic.AtomicInteger;

class Counter {
    private AtomicInteger count = new AtomicInteger(0);

    void increment() {
        count.incrementAndGet();
    }

    int getCount() {
        return count.get();
    }
}

3. 使用鎖(如 ReentrantLock):

使用顯式鎖(如 ReentrantLock)來同步訪問 volatile 變量的代碼塊。


import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

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

    void increment() {
        lock.lock();
        try {
            count++;
        } finally {
            lock.unlock();
        }
    }

    int getCount() {
        lock.lock();
        try {
            return count;
        } finally {
            lock.unlock();
        }
    }
}

使用volatile變量的正確使用場景

如果操作是簡單的讀寫,並且你只需要保證可見性,可以使用 volatile。但對於複合操作,可以使用上述其他方法來實現,通過這些方法,可以確保在多線程環境中對共享資源的正確同步和可見性。

user avatar u_13482808 头像 febobo 头像 seact 头像 willemwei 头像 aaaaaajie 头像 chuanghongdengdehoutao 头像 xc_xiang 头像 xiaoyongyong 头像
点赞 8 用户, 点赞了这篇动态!
点赞

Add a new 评论

Some HTML is okay.