kro*_*old 12 java multithreading synchronized thread-safety memory-visibility
我在一些OSS单元测试中经常看到这段代码,但它是否是线程安全的?while循环是否可以保证看到invoc的正确值?
如果不; nerd指向谁也知道哪个CPU架构可能会失败.
private int invoc = 0;
private synchronized void increment() {
invoc++;
}
public void isItThreadSafe() throws InterruptedException {
for (int i = 0; i < TOTAL_THREADS; i++) {
new Thread(new Runnable() {
public void run() {
// do some stuff
increment();
}
}).start();
}
while (invoc != TOTAL_THREADS) {
Thread.sleep(250);
}
}
Run Code Online (Sandbox Code Playgroud)
Nat*_*hes 17
不,这不是线程安全的.invoc需要声明为volatile,或者在同一个锁上同步时访问,或者更改为使用AtomicInteger.只是使用synchronized方法来增加invoc,但不是同步来读取它,这是不够的.
JVM进行了大量优化,包括CPU特定的缓存和指令重新排序.它使用volatile关键字和锁定来决定何时可以自由优化以及何时必须具有可供其他线程读取的最新值.因此,当读者不使用锁时,JVM无法知道不给它一个陈旧的值.
来自Java Concurrency in Practice(第3.1.3节)的引用讨论了如何同步写入和读取:
内部锁定可用于保证一个线程以可预测的方式看到另一个线程的影响,如图3.1所示.当线程A执行同步块,并且随后线程B进入由同一锁保护的同步块时,在释放锁之前A可见的变量值保证在获得锁时对B可见.换句话说,当同步块执行由同一个锁保护的同步块时,A在同步块中或之前所做的一切都是可见的.没有同步,就没有这样的保证.
下一节(3.1.4)涵盖了使用volatile:
Java语言还提供了另一种较弱的同步形式,即volatile变量,以确保对变量的更新可预测地传播到其他线程.当一个字段被声明为volatile时,编译器和运行时会注意到该变量是共享的,并且对它的操作不应该与其他内存操作重新排序.易失性变量不会缓存在寄存器或缓存中,而是隐藏在其他处理器中,因此读取volatile变量始终会返回任何线程的最新写入.
当我们桌面上都有单CPU机器时,我们会编写代码,直到它在多处理器盒上运行时才会出现问题,通常是在生产中.导致可见性问题的一些因素,如CPU本地缓存和指令重新排序,是您期望从任何多处理器机器中获得的东西.但是,对于任何机器都可能会消除明显不需要的指令.没有什么可以强迫JVM让读者看到变量的最新值,你受JVM实现者的支配.所以在我看来,这个代码不适合任何CPU架构.