簡體   English   中英

Java中易變的關鍵字用法

[英]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;

如果i0您認為您將得到2作為輸出,而這里是1

進行同步,如果不過度使用並且經過深思熟慮,這非常簡單。

建議同步閱讀

如果兩個線程都在讀取和寫入共享變量,則僅使用volatile關鍵字是不夠的。 在這種情況下,您需要使用同步來確保變量的讀寫是原子的。

因此,在修改i值時需要使用同步。

暫無
暫無

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

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