[英]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
進行鎖定:
正如您之前在評論中提到的那樣,即使您有三個單獨的原子步驟,線程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.