Java并发多读/原子写

时间:2013-03-18 15:41:28

标签: java concurrency

我正在尝试在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。

4 个答案:

答案 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,或将flagatom声明为volatile变量,否则不保证一个线程执行的并发修改是可见的在其他人身上。

您也可以使用计数为1的CountDownLatch,而不是等到flagtrue

答案 3 :(得分:0)

代码中的问题。 原子++引入了竞争条件as explained here 2.作者更新的标志值可能不会立即显示给读者 3.作者更新的 atom 值可能不会立即显示给读者

以下是完成线程安全所需的2个简单步骤。

  1. 正如Damien所指出的那样,使 atom AtomicInteger。
  2. Mark flag volatile