簡體   English   中英

實現原子操作,因為java volatile 保證發生在關系之前?

[英]achieve atomic operation because java volatile guarantees happens-before relation?

實現原子操作,因為java volatile 保證發生在關系之前?

我已經閱讀了有關 volatile 之前發生的信息:

如果線程 A 寫入 volatile 變量,而線程 B 隨后讀取同一個 volatile 變量,則線程 A 在寫入 volatile 變量之前可見的所有變量,在線程 B 讀取 volatile 變量后也將可見。

現在,我有兩個變量:

         static int m_x;
volatile static int m_y;

現在我有兩個線程,一個只寫他們,先寫m_x,然后寫m_y; 另一個,只從他們那里讀,先讀m_y,然后是m_x。

我的問題是:寫操作是原子的嗎? 讀操作是原子的嗎?

在我的理解中,它們應該是原子的:

(1)在寫線程方面,在(Write-1)之后,它不會將其緩存刷新到主內存,因為m_x不是易失性的,因此,讀線程無法看到更新; 在 (Write-2) 之后,它將其緩存刷新到主內存,因為 m_y 是易失性的;

(2) 在讀線程一側,在 (Read-1) 時,它將從主內存更新其緩存,因為 m_y 是易失性的; 並且在 (Read-2) 上,它不會從主內存更新其緩存,因為 m_x 不是易失性的。

由於以上兩個原因,我認為讀取線程應該始終觀察兩個變量的原子值。 對?

public class test {
         static int m_x;
volatile static int m_y;

public static void main(String[] args) {
    // write
    new Thread() {
        public
        void run() {
            while(true) {
                int x = randInt(1, 1000000);
                int y = -x;
                m_x = x; // (Write-1)
                m_y = y; // (Write-2)
            }
        }
    }.start();

    // read
    new Thread() {
        public
        void run() {
            while(true) {
                int y = m_y; // (Read-1)
                int x = m_x; // (Read-2)

                int sum = y + x;
                if (sum != 0) {
                    System.out.println("R:sum=" + sum);
                    System.out.println("R:x=" + x);
                    System.out.println("R:y=" + y);
                    System.out.println("\n");
                }
            }
        }
    }.start();
}

public static int randInt(int Min, int Max) {
    return Min + (int)(Math.random() * ((Max - Min) + 1));
}

}

如評論中所述,兩次讀取和寫入不是原子的。 使用volatile關鍵字無法實現原子volatile

這個事實可以在運行你的程序時觀察到。

要同時讀/寫這兩個變量,您要么需要適當的同步,要么創建自己的不可變值。

以后做

public class ConcurrencyTestApp {

    // use volatile for visibility
    private volatile ImmutableValue immutableValue = new ImmutableValue(0, 0); // initial, non-null value

    static class ImmutableValue {
        private final int x;
        private final int y;

        ImmutableValue(final int x, final int y) {
            this.x = x;
            this.y = y;
        }

        int getX() {
            return x;
        }

        int getY() {
            return y;
        }

        @Override
        public String toString() {
            return String.format("x = %s\t| y = %s", x, y);
        }
    }

    void replaceOldWithNewValue(final ImmutableValue newValue) {
        immutableValue = newValue;
    }

    ImmutableValue getImmutableValue() {
        return immutableValue;
    }

    static class Writer extends Thread {

        private final ConcurrencyTestApp app;

        Writer(ConcurrencyTestApp app) {
            this.app = app;
        }

        volatile boolean isRunning = true;

        @Override
        public void run() {
            while (isRunning) {
                int x = randInt(1, 1000000);
                int y = -x;

                app.replaceOldWithNewValue(new ImmutableValue(x, y));
            }

        }

        int randInt(int Min, int Max) {
            return Min + (int) (Math.random() * ((Max - Min) + 1));
        }
    }

    static class Reader extends Thread {
        private final ConcurrencyTestApp app;

        Reader(ConcurrencyTestApp app) {
            this.app = app;
        }

        volatile boolean isRunning = true;

        @Override
        public void run() {
            while (isRunning) {
                ImmutableValue value = app.getImmutableValue();
                System.out.println(value);
                int x = value.getX();
                int y = value.getY();
                int sum = x + y;
                if (sum != 0) {
                    System.out.println("R:sum=" + sum);
                    System.out.println("R:x=" + x);
                    System.out.println("R:y=" + y);
                    System.out.println("\n");
                }
            }
        }
    }

    public static void main(String[] args) {

        ConcurrencyTestApp app = new ConcurrencyTestApp();
        Writer w = new Writer(app);
        Reader r = new Reader(app);

        w.start();
        r.start();

        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }

        w.isRunning = false;
        r.isRunning = false;
    }

}

為了進一步參考,我推薦 Brian Goetz 和 Tim Peierls 合着的Java concurrency in practice一書。


附錄

...

由於以上兩個原因,我認為讀取線程應該始終觀察兩個變量的原子值。 對?

錯誤的!

......你錯過了一個重要的部分。

如需參考,請參閱Jeremy Manson 和 Brian Goetz 的 JSR 133(Java 內存模型)常見問題解答部分volatile 有什么作用?

在您的程序中,沒有什么可以阻止以下內容:

假設int m_x = x1int m_y = y1

  1. 你的 Writer-Thread 一直執行到Write-1
    • int m_x現在設置為值x2 (您的 Reader-Thread 可能會或可能不會看到該值)
  2. 您的 Writer-Thread 的執行被暫停(無論出於何種原因)
  3. 您的 Reader-Thread 執行Read-1Read-2 (沒有什么可以阻止 Reader-Thread 這樣做)
    • int y = m_y這仍然是y1因為你的 Writer-Thread 還沒有進一步執行
    • int x = m_x可能是x2 (但也可能是x1
  4. 您的 Reader-Thread 被暫停,而您的 Writer-Thread 繼續
    • int m_y現在被設置為值y2 (只有現在Read-1將獲得y2並且Read-2將保證獲得x2 - 除非您的 Writer-Thread 繼續)
  5. ... 等等

看到你自己修改了你的作者

System.out.println("W0");
m_x = x; // non-volatile
System.out.println("W1: " + x);
m_y = y; // volatile
System.out.println("W2: " + y);

和閱讀器線程代碼

System.out.println("R0");
int y = m_y; // volatile
System.out.println("R1: " + y);
int x = m_x; // non-volatile
System.out.println("R2: " + x);

那么為什么它不適合你呢?

從參考

... volatile 與否,線程 A 在寫入 volatile 字段 f 時可見的任何內容在線程 B 讀取 f 時都可見。

因此,你的閱讀器線程保證看到新的值m_xm_y只有當你的作家線程寫了新的價值m_y 但是因為沒有保證特定的線程執行順序,所以寫操作Write-2可能不會在執行Read-1之前發生。

另請參閱Jakob Jenkov 的 Java Volatile Keyword 以獲取與您類似的示例。

你斷言

“...在(Write-1)之后,它不會將其緩存刷新到主內存,因為 m_x 不是易失性的”

“...在 (Read-2) 上,它不會從主內存更新其緩存,因為 m_x 不是易失性的。”

事實上,無法確定緩存是否會被刷新(寫入),或者緩存中是否存在值(讀取)。 JLS 當然不會對非易失性變量的讀取和寫入做出 >>any<< 保證。 這些保證僅適用於對 volatile 變量的讀寫操作。

雖然您可能會在某些平台上觀察到程序的一致行為,但 JLS 不保證這種行為。

暫無
暫無

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

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