我正在尝试在java中实现cuncurrent read / atomic write。
static int atom = 0;
static boolean flag = false;
public static void main(String[] args) {
new Thread(new Reader()).start();
new Thread(new Reader()).start();
new Thread(new Reader()).start();
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
write();
}
}, 3000);
}
static void write() {
flag = true;
Thread.sleep(1000);
atom++;
flag = false;
}
static class Reader implements Runnable {
@Override
public void run() {
while (true) {
Thread.sleep(5);
if (flag) {
continue;
}
System.out.println(atom);
}
}
}
当我的线程读取atom var时,读取完成,直到flag标记为true,并在标志关闭后更改值后继续读取。
最好的方法是什么?使用同步块?
谢谢,Phaedra。
答案 0 :(得分:3)
多部分答案。在第1部分中,我将回答实际问题。在第2部分中,我将稍微进行一些编辑。
你在这里有各种各样的活动,包括数据竞赛。您没有任何事先发生的订单,因此JIT完全有权将其转为:
while(true) {
...
if (flag) {...}
}
进入这个:
boolean flagCache = flag;
while(true) {
if (flagCache) { ... }
}
请注意,flagCache
永远不会在第二个版本中更新。 JVM没有义务对其进行更新,因为flag
未标记为volatile
。
除此之外,你已经注意到write
中的竞争条件,你似乎已经注意到了,而synchronized
区块确实可以帮助那里。方法是创建单独的private static final Object lock = new Object()
,然后在write
方法中对其进行同步。这样,写入将在该对象上同步,而读取则不会。如果您选择此路线,则还应将atom
标记为volatile
。并且您绝对不需要或不希望Thread.sleep(1000)
方法中的write
,尤其不是synchronized
块内的atom
。如果你在写入上有任何并发性,那将会产生一个巨大的瓶颈。
(如果你做得对的话,实际上并不严格要求将volatile
标记为volatile flag
[你可以躲开从Thread.sleep(5)
给你的事先发生的保证],但是这是一个微妙而棘手的操作,最好避免,除非你真的想要优化它,并且还认为自己是JMM /并发方面的高级程序员。)
说到瓶颈,您busy wait循环中的flag
值得一提,这是一个有趣的权衡。没有它,读取线程可能会旋转很多,浪费CPU。但有了它,他们可以无缘无故地旋转太多。但实际上,您根本不需要atom
或忙碌等待循环。如果你只是将volatile
标记为AtomicInteger
并同步其写入,那么它的读取将会很好。在写入易失性字段和从中读取之前有一个先发生的边缘。
但我认为@DamianJeżewski就在这里。 synchronized
可以简单,轻松,高效地完成操作(因为它使用compare and set代替阻塞,可能是上下文切换java.util.concurrent.*
阻止。
多线程的一般方法几乎总是尝试使用高级构造(即volatile
中的内容),然后再使用低级构造(如synchronized
甚至{{1} },绝对在Object.wait/notify
之前。当然,这不是一个严格而快速的规则 - 只是一般指导原则。在这种情况下,看看有多少想法必须进入一个非常简单的要求 - 高度并发读取的线程安全写入 - AtomicInteger.incrementAndGet
为您提供“免费”。
答案 1 :(得分:2)
使用AtomicInteger
套餐中的java.util.concurrent.atomic
不是更好吗?
答案 2 :(得分:1)
您需要在类上synchronize
,或将flag
和atom
声明为volatile
变量,否则不保证一个线程执行的并发修改是可见的在其他人身上。
您也可以使用计数为1的CountDownLatch
,而不是等到flag
为true
。
答案 3 :(得分:0)
代码中的问题。 原子++引入了竞争条件as explained here 2.作者更新的标志值可能不会立即显示给读者 3.作者更新的 atom 值可能不会立即显示给读者
以下是完成线程安全所需的2个简单步骤。