假设:
- 只有一个特定的线程会设置某个引用字段(不是 long 或 double,所以写入它是原子的)
- 有任意数量的线程可能会读取相同的字段
- 稍微陈旧的读取是可以接受的(最多几秒钟)
在这种情况下,您是否需要 volatile 或 AtomicReference 或类似的东西?
这篇文章指出:
如果您严格遵守单写原则,则不需要内存屏障。
这似乎表明在我所描述的情况下,你真的不需要做任何特别的事情。
所以,这是我运行的一个测试,结果很奇怪:
import org.junit.Test;
public class ThreadTest {
int onlyWrittenByMain = 0;
int onlyWrittenByThread = 0;
@Test
public void testThread() throws InterruptedException {
Thread newThread = new Thread(new Runnable() {
@Override
public void run() {
do {
onlyWrittenByThread++;
} while (onlyWrittenByMain < 10 || onlyWrittenByThread < 10);
System.out.println("thread done");
}
});
newThread.start();
do {
onlyWrittenByMain++;
// Thread.yield();
// System.out.println("test");
// new Random().nextInt();
} while (onlyWrittenByThread < 10);
System.out.println("main done");
}
}
有时运行它会输出“线程完成”,然后永远挂起。有时它确实完成了。所以线程看到了主线程所做的更改,但显然 main 并不总是看到线程所做的更改?
如果我将系统放入,或者 Thread.yield,或者随机调用,或者使 onlyWrittenByThread 不稳定,它每次都会完成(尝试大约 10 次以上)。
这是否意味着我上面引用的博客文章不正确?即使在单一编写器场景中,您也必须有一个内存屏障?
没有人完全回答这个问题,所以我想我猜可能不需要内存屏障是正确的,但是如果没有创建之前发生关系的东西,java 编译器和热点可以进行优化(例如提升)会让它不为所欲为。