簡體   English   中英

java中的易失性和原子操作

[英]Volatile and atomic operation in java

我已閱讀有關Java中原子操作的文章,但仍有一些疑問需要澄清:

int volatile num;
public void doSomething() {
  num = 10;  // write operation
  System.out.println(num)   // read
  num = 20;  // write
  System.out.println(num);  // read
}

所以我在1方法上做了4次操作,它們是原子操作嗎? 如果多個線程同時調用doSomething()方法會發生什么?

如果沒有線程將看到中間狀態,則操作是原子的,即操作將完全完成,或者根本不完成。

讀取int字段是原子操作,即一次讀取所有32位。 編寫一個int字段也是原子的,該字段要么已完全寫入,要么根本不寫入。

但是,方法doSomething()不是原子的; 一個線程可能在執行該方法時將CPU輸出到另一個線程,並且該線程可能會看到已執行了一些但不是所有操作。

也就是說,如果線程T1和T2都執行doSomething(),則可能發生以下情況:

T1: num = 10;
T2: num = 10;
T1: System.out.println(num); // prints 10
T1: num = 20;
T1: System.out.println(num); // prints 20
T2: System.out.println(num); // prints 20
T2: num = 20;
T2: System.out.println(num); // prints 20

如果doSomething()被同步,其原子性將得到保證,並且上述場景是不可能的。

volatile確保如果你有一個線程A和一個線程B,那么兩者都會看到對該變量的任何更改。 因此,如果它在某個時刻線程A改變了這個值,那么線程B將來可能會看到它。

原子操作確保所述操作的執行“一步到位”。 這有點混亂,因為查看代碼'x = 10;' 可能看起來是“一步到位”,但實際上需要在CPU上執行幾個步驟。 可以通過多種方式形成原子操作,其中一種方法是使用synchronized進行鎖定:

  • volatile關鍵字承諾的內容。
  • 獲取對象的鎖(或在靜態方法的情況下為Class),並且沒有兩個對象可以同時訪問它。

正如您之前在評論中提到的那樣,即使您有三個單獨的原子步驟,線程A在某個時刻執行,但線程B有可能在這三個步驟的中間開始執行。 為了確保對象的線程安全性,必須將所有三個步驟組合在一起以完成一個步驟。 這是使用鎖的部分原因。

需要注意的一件非常重要的事情是,如果要確保兩個線程永遠不能同時訪問您的對象,則必須同步所有方法。 您可以在對象上創建一個非同步方法來訪問存儲在對象中的值,但這會損害類的線程安全性。

您可能對java.util.concurrent.atomic庫感興趣。 我也不是這方面的專家,所以我建議給我推薦一本書: Java Concurrency in Practice

每個人對volatile變量的讀寫都是原子的。 這意味着線程在讀取時不會看到num的值發生變化,但它仍然可以在每個語句之間發生變化。 因此,當其他線程執行相同操作時,運行doSomething線程將打印10或20,然后再打印10或20.在所有線程完成調用doSomething ,num的值將為20。

我根據Brian Roach的評論修改了我的答案。

它是原子的,因為在這種情況下它是整數。

易失性只能提高線程間的可見性,但不能提供原子能見度。 volatile可以讓你看到整數的變化,但不能在變化中進行整合。

例如,long和double可能會導致意外的中間狀態。

原子操作和同步:

原子執行在單個任務單元中執行,不會受到其他執行的影響。 多線程環境中需要進行原子操作以避免數據不規則。

如果我們正在讀/寫一個int值,那么它就是一個原子操作。 但通常如果它在方法內部,那么如果方法未同步,則許多線程可以訪問它,這可能導致值不一致。 但是,int ++不是原子操作。 因此,當一個線程讀取它的值並將其遞增1時,其他線程已讀取較舊的值,從而導致錯誤的結果。

為了解決數據不一致問題,我們必須確保count上的遞增操作是原子的,我們可以使用Synchronization來實現,但Java 5 java.util.concurrent.atomic提供了int和long的包裝類,可用於原子地實現不使用同步。

使用int可能會產生數據數據不一致,如下所示:

public class AtomicClass {

    public static void main(String[] args) throws InterruptedException {

        ThreardProcesing pt = new ThreardProcesing();
        Thread thread_1 = new Thread(pt, "thread_1");
        thread_1.start();
        Thread thread_2 = new Thread(pt, "thread_2");
        thread_2.start();
        thread_1.join();
        thread_2.join();
        System.out.println("Processing count=" + pt.getCount());
    }

}

class ThreardProcesing implements Runnable {
    private int count;

    @Override
    public void run() {
        for (int i = 1; i < 5; i++) {
            processSomething(i);
            count++;
        }
    }

    public int getCount() {
        return this.count;
    }

    private void processSomething(int i) {
        // processing some job
        try {
            Thread.sleep(i * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

輸出:計數值在5,6,7,8之間變化

我們可以使用java.util.concurrent.atomic來解決這個問題,它始終將計數值輸出為8,因為AtomicInteger方法incrementAndGet()以原子方式將當前值遞增1。 如下所示:

public class AtomicClass {

    public static void main(String[] args) throws InterruptedException {

        ThreardProcesing pt = new ThreardProcesing();
        Thread thread_1 = new Thread(pt, "thread_1");
        thread_1.start();
        Thread thread_2 = new Thread(pt, "thread_2");
        thread_2.start();
        thread_1.join();
        thread_2.join();
        System.out.println("Processing count=" + pt.getCount());
    }
}

class ThreardProcesing implements Runnable {
    private AtomicInteger count = new AtomicInteger();

    @Override
    public void run() {
        for (int i = 1; i < 5; i++) {
            processSomething(i);
            count.incrementAndGet();
        }
    }

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

    private void processSomething(int i) {
        // processing some job
        try {
            Thread.sleep(i * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

來源: java中的原子操作

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM