这种检测心跳线程安全且一致吗?

时间:2023-01-21 06:57:14

This question has been discussed in two blog posts (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/), but I haven't heard a definitive answer yet. If we have one thread that does this:

这个问题已在两篇博文中讨论过(http://dow.ngra.de/2008/10/27/when-systemcurrenttimemillis-is-too-slow/,http://dow.ngra.de/2008/10 / 28 /我们真正知道关于非阻塞并发性的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++;
    }
  }
}

And many clients that run the following:

许多客户运行以下内容:

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

is it threadsafe or not?

是不是线程安全?

2 个解决方案

#1


5  

Within the java memory model? No, you are not ok.

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

I've seen a number of attempts to head towards a very 'soft flush' approach like this, but without an explicit fence, you're definitely playing with fire.

我已经看到了许多尝试朝着像这样的非常“软冲洗”的方法,但没有明确的围栏,你肯定会玩火。

The 'happens before' semantics in

'发生在'之前的语义

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

start to referring to purely inter-thread actions as 'actions' at the end of 17.4.2. This drives a lot of confusion since prior to that point they distinguish between inter- and intra- thread actions. Consequently, the intra-thread action of manipulating counter isn't explicitly synchronized across the volatile action by the happens-before relationship. You have two threads of reasoning to follow about synchronization, one governs local consistency and is subject to all the nice tricks of alias analysis, etc to shuffle operations The other is about global consistency and is only defined for inter-thread operations.

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

One for the intra-thread logic that says within the thread the reads and writes are consistently reordered and one for the inter-thread logic that says things like volatile reads/writes and that synchronization starts/ends are appropriately fenced.

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

The problem is the visibility of the non-volatile write is undefined as it is an intra-thread operation and therefore not covered by the specification. The processor its running on should be able to see it as it you executed those statements serially, but its sequentialization for inter-thread purposes is potentially undefined.

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

Now, the reality of whether or not this can affect you is another matter entirely.

现在,这是否会对你产生影响的现实完全是另一回事。

While running java on x86 and x86-64 platforms? Technically you're in murky territory, but practically the very strong guarantees x86 places on reads and writes including the total order on the read/write across the access to cacheflush and the local ordering on the two writes and the two reads should enable this code to execute correctly provided it makes it through the compiler unmolested. That assumes the compiler doesn't step in and try to use the freedom it is permitted under the standard to reorder operations on you due to the provable lack of aliasing between the two intra-thread operations.

在x86和x86-64平台上运行java时?从技术上讲,你处于阴暗的境界,但实际上非常强大的保证了x86对读写的影响,包括访问cacheflush时读/写的总顺序以及两次写入的本地排序和两次读取应启用此代码正确执行只要它通过编译器不受干扰。这假设编译器没有介入并尝试使用标准下允许的*来重新排序对您的操作,因为两个内部线程操作之间可证明缺少别名。

If you move to a memory with weaker release semantics like an ia64? Then you're back on your own.

如果你移动到像ia64这样的弱发布语义的内存?那你就回来了。

A compiler could in perfectly good faith break this program in java on any platform, however. That it functions right now is an artifact of current implementations of the standard, not of the standard.

但是,编译器可以完全真诚地在任何平台上破解java中的程序。它现在起作用是标准的当前实现的工件,而不是标准。

As an aside, in the CLR, the runtime model is stronger, and this sort of trick is legal because the individual writes from each thread have ordered visibility, so be careful trying to translate any examples from there.

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

#2


1  

Well, I don't think it is.

好吧,我认为不是。

The first if-statement:

第一个if语句:

if (counter == HeartBeatThread.counter) 
    return;

Does not access any volatile field and is not synchronized. So you might read stale data forever and never get to the point of accessing the volatile field.

不访问任何易失性字段且未同步。因此,您可能会永远读取陈旧数据,并且永远无法访问volatile字段。

Quoting from one of the comments in the second blog entry: "anything that was visible to thread A when it writes to volatile field f becomes visible to thread B when it reads f." But in your case B (the client) never reads f (=cacheFlush). So changes to HeartBeatThread.counter do not have to become visible to the client.

引用第二篇博客文章中的一条评论:“线程A在写入易失性字段f时可见的任何内容在读取f时都会被线程B看到。”但在你的情况下,B(客户端)永远不会读取f(= cacheFlush)。因此,对HeartBeatThread.counter的更改不必对客户端可见。

#1


5  

Within the java memory model? No, you are not ok.

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

I've seen a number of attempts to head towards a very 'soft flush' approach like this, but without an explicit fence, you're definitely playing with fire.

我已经看到了许多尝试朝着像这样的非常“软冲洗”的方法,但没有明确的围栏,你肯定会玩火。

The 'happens before' semantics in

'发生在'之前的语义

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

start to referring to purely inter-thread actions as 'actions' at the end of 17.4.2. This drives a lot of confusion since prior to that point they distinguish between inter- and intra- thread actions. Consequently, the intra-thread action of manipulating counter isn't explicitly synchronized across the volatile action by the happens-before relationship. You have two threads of reasoning to follow about synchronization, one governs local consistency and is subject to all the nice tricks of alias analysis, etc to shuffle operations The other is about global consistency and is only defined for inter-thread operations.

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

One for the intra-thread logic that says within the thread the reads and writes are consistently reordered and one for the inter-thread logic that says things like volatile reads/writes and that synchronization starts/ends are appropriately fenced.

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

The problem is the visibility of the non-volatile write is undefined as it is an intra-thread operation and therefore not covered by the specification. The processor its running on should be able to see it as it you executed those statements serially, but its sequentialization for inter-thread purposes is potentially undefined.

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

Now, the reality of whether or not this can affect you is another matter entirely.

现在,这是否会对你产生影响的现实完全是另一回事。

While running java on x86 and x86-64 platforms? Technically you're in murky territory, but practically the very strong guarantees x86 places on reads and writes including the total order on the read/write across the access to cacheflush and the local ordering on the two writes and the two reads should enable this code to execute correctly provided it makes it through the compiler unmolested. That assumes the compiler doesn't step in and try to use the freedom it is permitted under the standard to reorder operations on you due to the provable lack of aliasing between the two intra-thread operations.

在x86和x86-64平台上运行java时?从技术上讲,你处于阴暗的境界,但实际上非常强大的保证了x86对读写的影响,包括访问cacheflush时读/写的总顺序以及两次写入的本地排序和两次读取应启用此代码正确执行只要它通过编译器不受干扰。这假设编译器没有介入并尝试使用标准下允许的*来重新排序对您的操作,因为两个内部线程操作之间可证明缺少别名。

If you move to a memory with weaker release semantics like an ia64? Then you're back on your own.

如果你移动到像ia64这样的弱发布语义的内存?那你就回来了。

A compiler could in perfectly good faith break this program in java on any platform, however. That it functions right now is an artifact of current implementations of the standard, not of the standard.

但是,编译器可以完全真诚地在任何平台上破解java中的程序。它现在起作用是标准的当前实现的工件,而不是标准。

As an aside, in the CLR, the runtime model is stronger, and this sort of trick is legal because the individual writes from each thread have ordered visibility, so be careful trying to translate any examples from there.

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

#2


1  

Well, I don't think it is.

好吧,我认为不是。

The first if-statement:

第一个if语句:

if (counter == HeartBeatThread.counter) 
    return;

Does not access any volatile field and is not synchronized. So you might read stale data forever and never get to the point of accessing the volatile field.

不访问任何易失性字段且未同步。因此,您可能会永远读取陈旧数据,并且永远无法访问volatile字段。

Quoting from one of the comments in the second blog entry: "anything that was visible to thread A when it writes to volatile field f becomes visible to thread B when it reads f." But in your case B (the client) never reads f (=cacheFlush). So changes to HeartBeatThread.counter do not have to become visible to the client.

引用第二篇博客文章中的一条评论:“线程A在写入易失性字段f时可见的任何内容在读取f时都会被线程B看到。”但在你的情况下,B(客户端)永远不会读取f(= cacheFlush)。因此,对HeartBeatThread.counter的更改不必对客户端可见。