3

这个问题已经在两篇博http://dow.ngra.de/2008/10/27/when-systemcurrenttimemillis-is-too-slow/,http://dow.ngra.de/2008/10 /28/what-do-we-really-know-about-non-blocking-concurrency-in-java/),但我还没有听到明确的答案。如果我们有一个线程这样做:

public class HeartBeatThread extends Thread {
  public static int counter = 0;
  public static volatile int cacheFlush = 0;

  public HeartBeatThread() {
    setDaemon(true);
  }

  static {
    new HeartBeatThread().start();
  }

  public void run() {   
    while (true) {     
      try {
        Thread.sleep(500);
      } catch (InterruptedException e) {
        throw new RuntimeException(e);
      }

      counter++;
      cacheFlush++;
    }
  }
}

以及许多运行以下命令的客户端:

if (counter == HeartBeatThread.counter) return;
counter = HeartBeatThread.cacheFlush;

它是线程安全的吗?

4

2 回答 2

5

在java内存模型中?不,你不行。

我已经看到许多尝试像这种非常“软同花”的方法,但如果没有明确的围栏,你肯定是在玩火。

中的“发生在”语义

http://java.sun.com/docs/books/jls/third_edition/html/memory.html#17.7

在 17.4.2 结束时开始将纯线程间操作称为“操作”。这引起了很多混乱,因为在此之前它们区分了线程间和线程内的操作。因此,操作计数器的线程内操作不会通过发生之前的关系在易失性操作之间显式同步。您有两个关于同步的推理线程,一个管理本地一致性,并受到别名分析等所有漂亮技巧的影响,以进行混洗操作。另一个是关于全局一致性,只为线程间操作定义。

一种用于线程内逻辑,表示在线程内读取和写入一致地重新排序,另一种用于线程间逻辑,表示诸如易失性读/写之类的事情,并且同步开始/结束被适当地隔离。

问题是非易失性写入的可见性未定义,因为它是线程内操作,因此未包含在规范中。它运行的处理器应该能够在您串行执行这些语句时看到它,但它用于线程间目的的顺序化可能是未定义的。

现在,这是否会影响您的现实完全是另一回事。

在 x86 和 x86-64 平台上运行 java 时?从技术上讲,您处于模糊的领域,但实际上 x86 对读取和写入提供了非常强大的保证,包括访问 cacheflush 的读取/写入的总顺序以及两次写入和两次读取的本地排序应该启用此代码正确执行,前提是它可以通过编译器不受干扰。这假设编译器不会介入并尝试使用标准允许的自由来对您的操作重新排序,因为两个线程内操作之间可证明缺乏别名。

如果你移动到一个释放语义较弱的内存,比如 ia64?然后你自己回来了。

然而,编译器可以完全善意地在任何平台上用 java 破解这个程序。它现在起作用是标准的当前实现的产物,而不是标准的产物。

顺便说一句,在 CLR 中,运行时模型更强大,并且这种技巧是合法的,因为来自每个线程的单独写入具有有序的可见性,因此请小心尝试从那里翻译任何示例。

于 2008-11-07T21:33:06.527 回答
1

嗯,我不这么认为。

第一个 if 语句:

if (counter == HeartBeatThread.counter) 
    return;

不访问任何 volatile 字段并且不同步。因此,您可能会永远读取过时的数据,而永远无法访问 volatile 字段。

引用第二篇博客文章中的一条评论:“线程 A 在写入 volatile 字段 f 时可见的任何内容在线程 B 读取 f 时都将变为可见。” 但在您的情况下,B(客户端)从不读取 f(=cacheFlush)。因此,对 HeartBeatThread.counter 的更改不必对客户端可见。

于 2008-11-07T21:27:34.477 回答