实现原子操作,因为java volatile保证发生在关系之前?
我已经读过关于volatile的发生之前的事情:
如果线程A写入易失性变量而线程B随后读取相同的易变量,那么在写入volatile变量之前,线程A可见的所有变量在读取volatile变量后也将对线程B可见。
现在,我有两个变量:
static int m_x;
volatile static int m_y;
现在我有两个线程,一个只写入它们,然后先写入m_x,然后写入m_y;另一个只读取它们,首先读取m_y,然后读取m_x。
我的问题是:写操作是原子的吗?读操作是原子吗?
根据我的理解,它们应该是原子的:
(1)在写入线程一侧,在写入(写入1)之后,它不会将其高速缓存刷新到主存储器,因为m_x是非易失性的,因此,读取线程无法看到更新;在(写入-2)之后,它将其缓存刷新到主存储器,因为m_y是易失性的;
(2)在读取线程一侧,在(读取1)上,它将从主存储器更新其缓存,因为m_y是易失性的;在(读取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));
}
}
答案 0 :(得分:1)
如评论中所述,两次读写都不是原子的。使用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并发实践 一书。
<强>附录强>
...
由于上述两个原因,我认为读线程应该始终如此 观察两个变量的原子值。正确?
<强> 错! 强>
......你错过了重要的一部分。
有关参考,请参阅JSR 133 (Java Memory Model) FAQ by Jeremy Manson and Brian Goetz部分 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);
那为什么它不适合你?
来自参考资料
...易失性,线程A在写入易失性字段f时可见的任何内容在读取f时都会被线程B看到。
因此,只有在您的Writer线程将新值写入m_x
时,您的Reader线程才能保证看到m_y
和m_y
的新值。但是因为没有保证特定的线程执行顺序,所以在Write-2
执行之前可能不会发生写操作Read-1
。
另请参阅Java Volatile Keyword by Jakob Jenkov了解与您类似的示例。
答案 1 :(得分:1)
你断言
“...之后(写入1),它不会将其缓存刷新到主存储器,因为m_x不是易失性的”
和
“... on(Read-2),它不会从主内存更新缓存,因为m_x不易变。”
实际上,无法判断缓存是否会被刷新(写入),或者缓存中是否存在值(读取)。 JLS肯定不会使&gt;&gt;任何&lt;&lt;保证读取和写入非易失性变量。保证仅适用于volatile变量的读写操作。
虽然您可能会在某些平台上观察到程序的一致行为,但JLS无法保证这种行为。