这个问题已经在两篇博文(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;
是不是线程安全?
答案 0 :(得分: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中,运行时模型更强大,这种技巧是合法的,因为每个线程的单个写入都具有有序的可见性,因此请小心尝试从那里翻译任何示例。
答案 1 :(得分:1)
嗯,我认为不是。
第一个if语句:
if (counter == HeartBeatThread.counter)
return;
不访问任何易失性字段且未同步。因此,您可能会永远读取过时的数据,并且永远无法访问volatile字段。
引用第二篇博客文章中的一条评论:“线程A在写入易失性字段f时可见的任何内容在读取f时都会显示给线程B.” 但在你的情况下,B(客户端)永远不会读取f(= cacheFlush)。因此,对HeartBeatThread.counter的更改不必对客户端可见。