[英]Volatile keyword usage in Java
我無法理解,如果我的變量既是volatile變量又是static變量,那么為什么線程未在輸出中反映公共共享值?最后幾行的輸出是:線程正在運行4998Thread-0線程正在運行4999Thread-0線程正在運行4899Thread -1線程正在運行
public class Test implements Runnable{
volatile static int i=0;
@Override
public void run() {
for(;i<5000;i++)
{ try {
Thread t = Thread.currentThread();
String name = t.getName();
// Thread.sleep(10);
System.out.println(i+name);
} catch (Exception ex) {
ex.printStackTrace();
}
System.out.println("Thread is running");
}}
public static void main(String[] args) {
Test t=new Test();
Thread t1=new Thread(t);
Thread t2=new Thread(t);
t1.start();
// t.run();
t2.start();
}
}
您不能對volatile變量使用像i++
這樣的復合(多步驟)操作。
您可以讓兩個線程都檢索該值,將其增加並寫回,從而導致丟失一個增量(如您在示例中所看到的)。
易失性確保變量的更改對其他線程可見。 但是它不能確保同步 。
從程序的一次執行中,我得到以下輸出:
Thread is running
3474Thread-1
Thread is running
3475Thread-0
Thread is running
3477Thread-0
(.... many lines where i goes from 3478 to 4998, with Thread-0 always being the one running...)
Thread is running
4999Thread-0
Thread is running
3476Thread-1
Thread is running
發生這種情況是因為線程獲得了要運行的處理器時間的分片,並且它們的執行可以在任何時候暫停和恢復。 這里的線程1正在執行“ System.out.println(i + name);”行。 我的值為3476。i+ name的值評估為“ 3476Thread-1”,但此時Thread-1的執行停止,而Thread-0獲得其時間片。 線程-0執行直到完成。 然后線程1再次執行。 在將 i + name評估為“ 3476Thread-1”之后以及在調用println之前,我們已將其保留。 調用現在已完成並打印,因此您將在結束時看到“ 3476Thread-1”。 我已經被Thread-0增加到5000了,但是這並沒有改變i + name的求值結果,這是在所有這些增加之前完成的。
問題在於i ++和i + name是不同的指令,線程執行可以在它們之間暫停和恢復。 為了確保獲得保密輸出,需要確保i ++和i + name之間沒有中斷。 也就是說,您需要使該指令集原子化。
public class Test implements Runnable{
static Object lock = new Object();
volatile static int i=0;
@Override
public void run() {
for(;;)
{
try {
Thread t = Thread.currentThread();
String name = t.getName();
synchronized( lock )
{
if ( i>=5000 )
break;
i++;
System.out.println(i+name);
}
// Thread.sleep(10);
} catch (Exception ex) {
ex.printStackTrace();
System.out.println("Thread is running");
}
} }
public static void main(String[] args) {
Test t=new Test();
Thread t1=new Thread(t);
Thread t2=new Thread(t);
t1.start();
// t.run();
t2.start();
}
}
在該程序中,如果Thread-1在i ++和i + name之間暫停,則它將位於由synced(lock)控制的關鍵區域內。 當線程0開始執行時,它將到達synced(lock)指令,並且必須停止執行,直到線程1恢復並退出該塊為止。 因為JLS確保:
同步語句(第14.19節)計算對對象的引用; 然后,它嘗試在該對象的監視器上執行鎖定操作,並且在鎖定操作成功完成之前不會進一步進行操作。 執行鎖定操作后,將執行synced語句的主體。 如果主體的執行正常或突然完成,則會在同一監視器上自動執行解鎖動作。
這是我從中學習了volatile關鍵字的用法的代碼片段:
import java.util.concurrent.TimeUnit;
public class VolatileDemo {
private volatile static boolean stop; //remove volatile keyword to see the difference
private static int i = 0;
public static void main(String[] args) throws InterruptedException {
Thread otherThread = new Thread(new Runnable() {
public void run() {
while (!stop)
i++;
}
});
otherThread.start();
TimeUnit.SECONDS.sleep(1);
stop = true;// main thread stops here and should stop otherThread as well
}
}
如果要觀察volatile的作用,請嘗試將其刪除,然后再執行,這在運行這兩個版本后應該很明顯,但是基本上這里的關鍵字可以防止Java編譯器假設停止條件永遠不會改變,它將在每次讀取時讀取條件得到評估的時間。 整潔,不是嗎?
通過查看您的代碼,問題不在於使用volatile關鍵字,問題在於表達式i++
不是原子的。 它實際上是三步操作:
1) fetch value of i;
2) increment i;
3) save new value of i
當多線程起作用時,這些線程可能會也可能不會與其他線程的指令混合在一起。 因此執行也可能像這樣:
1) T1: fetch i;
2) T2: fetch i;
3) T1: increment i;
4) T2: increment i;
5) T1: save i;
6) T2: save i;
如果i
是0
您認為您將得到2
作為輸出,而這里是1
。
進行同步,如果不過度使用並且經過深思熟慮,這非常簡單。
建議同步閱讀
如果兩個線程都在讀取和寫入共享變量,則僅使用volatile關鍵字是不夠的。 在這種情況下,您需要使用同步來確保變量的讀寫是原子的。
因此,在修改i值時需要使用同步。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.