[英]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
= x1和int m_y
= y1
Write-1
int m_x
現在設置為值x2 (您的 Reader-Thread 可能會或可能不會看到該值)Read-1
和Read-2
(沒有什么可以阻止 Reader-Thread 這樣做)
int y
= m_y
這仍然是y1因為你的 Writer-Thread 還沒有進一步執行int x
= m_x
可能是x2 (但也可能是x1 )int m_y
現在被設置為值y2 (只有現在Read-1
將獲得y2並且Read-2
將保證獲得x2 - 除非您的 Writer-Thread 繼續)看到你自己修改了你的作者
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_x
和m_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.