0

我正在尝试在 java 中实现一个 cuncurrent 读取/原子写入。

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);
    }
  }
}

当我的线程读取原子变量时,读取完成,直到标志标记为真,并在标志关闭时在值更改后继续。

做这个的最好方式是什么?使用同步块?

谢谢,费德拉。

4

4 回答 4

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方法内同步。这样,写入将在该对象上同步,而读取则不会。如果你走这条路线,你也应该标记atomvolatile。而且您绝对不需要或不希望使用Thread.sleep(1000)write方法,尤其是不在一个synchronized块内。如果您在写入时有任何类型的并发性,那将造成很大的瓶颈。

(如果你做对了事情,实际上并不严格需要标记atomvolatile[你可以捎带前发生的事情 - 保证阅读 fromvolatile flag给你],但这是一个微妙而棘手的操作,除非你真的试图优化,否则最好避免见鬼,并且还认为自己是 JMM/并发方面的高级程序员。)

说到瓶颈,你Thread.sleep(5)繁忙的等待循环中作为一个有趣的权衡值得一提。没有它,读取线程可能会大量旋转,从而浪费 CPU。但是有了它,他们可能会无缘无故地旋转太多。但实际上,您根本不需要flag,或者忙等待循环。如果您只是将其标记atomvolatile并同步其写入,那么它的读取将正常进行。在写入 volatile 字段和读取它之间存在先发优势。


但我认为@Damian Jeżewski 就在这里。AnAtomicInteger可以简单、轻松且更有效地完成这个技巧(因为它使用比较和设置而不是阻塞的、潜在的上下文切换synchronized块)。

多线程的一般方法应该几乎总是在使用低级构造之前尝试使用高级构造(即,在 中的东西java.util.concurrent.*)(例如volatile或什synchronized至,并且绝对是在之前Object.wait/notify)。当然,这不是一个硬性规定——只是一般准则。在这种情况下,看看有多少想法必须进入一个非常简单的要求 - 具有高并发读取的线程安全写入 -AtomicInteger.incrementAndGet为您提供“免费”。

于 2013-03-18T15:59:54.867 回答
2

AtomicIntegerjava.util.concurrent.atomic包装中使用不是更好吗?

于 2013-03-18T15:43:50.100 回答
1

您需要synchronize在类上,或将和声明flagatom变量volatile,否则一个线程执行的并发修改不能保证在其他线程上可见。

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

于 2013-03-18T15:45:04.167 回答
0

代码中的问题。1. atom++ 引入了这里解释的竞争条件 2. writer 更新的标志值可能不会立即对 reader 可见 3. writer 更新的atom值可能不会立即对 reader 可见

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

  1. 正如 Damien 所指出的,制作原子AtomicInteger。
  2. 标记标志易变
于 2013-10-05T17:10:40.877 回答