实现原子操作,因为java volatile保证发生在关系之前?
我之前读过关于volatile的报道:
如果线程A写入一个易挥发变量,线程B随后读取相同的易挥发变量,那么线程A在写入易挥发变量之前可见的所有变量,在线程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) 在读线程的一侧,on(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));
}
}
你断言
"...(写-1)后,它不会刷新其缓存到主存,因为m_x不是易失性的"
而且
“…在(Read-2)上,它不会从主存更新缓存,因为m_x不是易失性的。”
事实上,无法判断缓存是否将被刷新(写入),或者缓存中是否存在值(读取)。JLS肯定不会让人失望
虽然您可能会在某些平台上观察到程序的一致行为,但JLS并不能保证这种行为。
如注释中所述,这两个读写不是原子的。使用易失性
关键字无法实现原子性。
这个事实可以在运行程序时观察到。
要同时读/写这两个变量,要么需要适当的同步,要么创建自己的不可变值。
以后再做
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 PeierlsJava实践中的并发这本书。
补遗
...
由于以上两个原因,我认为读取线程应该始终观察两个变量的原子值。正当
错误的
...你错过了一个重要的部分。
如需参考,请参阅Jeremy Manson和Brian Goetz的JSR 133(Java内存模型)常见问题解答部分volatile做什么?
在您的程序中,没有任何东西可以阻止以下操作:
假设int m_x
=x1和int m_y
=y1
Write-1
int y
=m_y
仍然是y1,因为您的Writer线程尚未进一步执行int m_y
现在被设置为值y2(只有现在Read-1
才会得到y2,Read-2
才会保证得到x2,除非你的Writer线程继续)看到你自己修改你的作者
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时对其可见的任何内容在线程B读取f时对其可见。
因此,只有当写入线程将新值写入m_y
时,你的读线程才能保证看到m_x
和m_y
的新值。但是,由于没有特定的线程执行顺序得到保证,在执行Read-1
之前,写操作write-2
可能不会发生。
另请参阅Jakob JenkovJavaVolatile Keyword的类似示例。
我有一个关于Java中代码重新排序和竞争条件的问题。 假设我有以下代码,有2个或多个线程同时执行: JVM是否可能以错误的顺序执行此操作?例如,以下重新排序是否可能?: 还是保证锁不会被重新排序?
更多面试题总结请看:【面试题】技术面试题汇总 malloc / free 简介 void *malloc(size_t size) void free(void *ptr) malloc 分配指定大小的内存空间,返回一个指向该空间的指针。大小以字节为单位。返回 void* 指针,需要强制类型转换后才能引用其中的值。 free 释放一个由 malloc 所分配的内存空间。ptr 指向一个要释放内存
其中“url”是JSON格式的,它使用http GET方法返回大量数据。 我无法从我在getResultForRequest(url)方法中传递的“url”中获取JSON格式的数据。我在urlConnection.connect();中出错。Internet权限也在AndroidManifest.xml文件中给出。 这是我的日志。 提前谢谢。
简介 在 DB 中 Swoft 提供了 select、selectOne、update、insert、delete、cursor、statement、affectingStatement、unprepared 方法进行原生操作。 新增数据 $bool = DB::insert('INSERT INTO users (`id`, `name`) VALUES (?, ?)', [1, 'Swoft'
主要内容:常用的原子操作命令维护原子性的推荐方法是保留所有相关信息,并将这些信息使用嵌入式文档的形式更新到文档中,这将确保单个文档的所有更新都是原子的。假设我们已经创建了一个名为 productDetails 的集合,并在其中插入了一个文档,如下所示: 在上面的文档中,我们将购买产品的客户的信息嵌入到 product_bought_by 字段中。当有新客户购买该产品时,我们首先会使用 product_available 字段
在使用 ConcurrentHashMap 时,ConcurentHashMap 通过 CAS 保证了操作的线程安全。但是当我们需要进行多个操作和复合操作时,ConcurentHashMap 并不能保证操作的原子性: get 和 put 分别是线程安全的,然而当我们先 get 后 put 操作时,从 get 到 put 的过程中间,其他线程可能已经修改了这个key对应的值。 ConcurrentH