这个问题必须在两个博客文章讨论(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++; } } }
许多客户运行以下内容:
if (counter == HeartBeatThread.counter) return; counter = HeartBeatThread.cacheFlush;
是不是线程安全?
在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中,运行时模型更强大,这种技巧是合法的,因为每个线程的单个写入都具有有序可见性,因此请务必从那里翻译任何示例.