实现原子操作是因为java的易失性保证发生在关系之前吗?
我以前读过关于易失性发生的事情:
如果线程A写入易失性变量,而线程B随后读取相同的易失性变量,则线程A在写入易失性变量之前可见的所有变量,在线程B读取易失性变量之后也将对线程B可见。
现在,我有两个变量:
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)在读线程方面,由于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));
}}
发布于 2015-12-23 06:01:18
正如注释中所述,这两个读取和写入不是原子的。不能通过使用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一书。
附录
...
由于以上两个原因,我认为读线程应该始终观察这两个变量的原子值。对吗?
错误!
...and你遗漏了一个重要的部分。
有关参考,请参阅JSR 133 (Java Memory Model) FAQ by Jeremy Manson and Brian Goetz小节易失性是做什么的?
在您的程序中,没有任何东西会阻止以下操作:
假设int m_x = x1,int m_y = y1
Write-1 x2设置为值(可能对您的写入器可见,也可能不可见
Read-1和Read-2 (没有什么能阻止读取器线程这样做) int y = m_y,它仍然是y1,因为你的写入器线程没有进一步执行yetint x = m_x,它可能是x2 (但它仍然可以是x2
int m_y现在被设置为值y2 (只有现在Read-1将获得y2,Read-2将被保证获得x2 -除非你的写入器线程continues)
等
来看看你自己修改了你的作者
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写入易失性字段f时对线程A可见的内容,在线程B读取f时都会变为可见。
因此,只有当你的写入器线程你的写入器线程将新值写入m_y时,你的读取器线程才能看到m_x和m_y 的新值。但是因为不能保证特定的线程执行顺序,所以在执行Read-1之前可能不会发生写操作Write-2。
另请参阅Java Volatile Keyword by Jakob Jenkov,以获取与您的示例类似的示例。
发布于 2015-12-24 19:25:19
你断言
"...after (Write-1),它不会将其缓存刷新到主内存,因为m_x不是易失性的“
和
"... on (Read-2),它不会从主内存更新其缓存,因为m_x不是易失性的。“
事实上,不可能说缓存是否会被刷新(写),或者缓存中是否存在值(读)。JLS当然不会为对非易失性变量的读写提供>>any<<保证。这些保证仅适用于对易失性变量的读写操作。
虽然您可能会在某些平台上观察到程序的一致行为,但JLS并不保证这种行为。
https://stackoverflow.com/questions/34422966
复制相似问题