代码之家  ›  专栏  ›  技术社区  ›  Jevgeni Kabanov

这种检测心跳的方法是否安全且一致?

  •  3
  • Jevgeni Kabanov  · 技术社区  · 17 年前

    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;
    

    它是否线程安全?

    2 回复  |  直到 17 年前
        1
  •  5
  •   Edward Kmett    17 年前

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

    我见过很多人试图采用这种非常“软刷新”的方法,但如果没有明确的围栏,你肯定是在玩火。

    中的“before”语义

    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中,运行时模型更强大,而且这种技巧是合法的,因为来自每个线程的各个写操作都具有有序的可见性,因此请小心尝试从中翻译任何示例。

        2
  •  1
  •   jiriki    17 年前

    嗯,我想不是。

    第一个if语句:

    if (counter == HeartBeatThread.counter) 
        return;
    

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

    引用第二篇博文中的一条评论:“线程A在写入易失性字段f时可以看到的任何内容,在线程B读取f时都可以看到。”