整数的非同步读取在java中是线程安全的吗?

整数的非同步读取在java中是线程安全的吗?,java,multithreading,thread-safety,synchronized,memory-visibility,Java,Multithreading,Thread Safety,Synchronized,Memory Visibility,我在一些OSS单元测试中经常看到这段代码,但它是线程安全的吗?while循环是否保证看到invoc的正确值 若否,;书呆子指出,谁也知道这可能会在哪个CPU体系结构上发生故障 private int invoc = 0; private synchronized void increment() { invoc++; } public void isItThreadSafe() throws InterruptedException { for (int

我在一些OSS单元测试中经常看到这段代码,但它是线程安全的吗?while循环是否保证看到invoc的正确值

若否,;书呆子指出,谁也知道这可能会在哪个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);
      }
  }
private int invoc=0;
私有同步的void增量(){
invoc++;
}
public void isItThreadSafe()引发InterruptedException{
对于(int i=0;i<总线程数;i++){
新线程(newrunnable()){
公开募捐{
//做点什么
增量();
}
}).start();
}
while(invoc!=总线程数){
睡眠(250);
}
}

不,它不是线程安全的。invoc需要声明为volatile,或在同一锁上同步时访问,或更改为使用AtomicInteger。仅仅使用synchronized方法来增加invoc,而不同步读取invoc是不够的

JVM进行了很多优化,包括特定于CPU的缓存和指令重新排序。它使用volatile关键字和锁定来决定何时可以自由优化,何时必须有一个最新的值供其他线程读取。因此,当读取器不使用锁时,JVM无法知道不给它一个过时的值

这段引自Java并发实践(第3.1.3节)的话讨论了如何同步写入和读取:

如图3.1所示,内部锁定可用于确保一个线程以可预测的方式看到另一个线程的影响。当线程A执行一个同步块,随后线程B进入由同一个锁保护的同步块时,在释放锁之前对A可见的变量值保证在获取锁时对B可见。换句话说,当B执行由同一锁保护的同步块时,A在同步块中或之前所做的一切都对B可见。没有同步,就没有这样的保证

下一节(3.1.4)将介绍如何使用挥发油:

Java语言还提供了另一种更弱的同步形式,即可变变量,以确保变量的更新可预测地传播到其他线程。当一个字段被声明为volatile时,编译器和运行时会注意到这个变量是共享的,并且对它的操作不应该与其他内存操作一起重新排序。易失性变量不缓存在寄存器或缓存中,在这些寄存器或缓存中,它们对其他处理器隐藏,因此对易失性变量的读取总是返回任何线程最近的写入

当我们的桌面上都有单CPU机器时,我们会编写代码,直到它在多处理器机器上运行(通常是在生产环境中)才会出现问题。引起可视性问题的一些因素,如CPU本地缓存和指令重新排序,是任何多处理器机器都会遇到的问题。然而,任何机器都可能会消除明显不需要的指令。没有什么能强迫JVM让读者看到变量的最新值,你只能任由JVM实现者摆布。因此,在我看来,这段代码对于任何CPU架构都不是一个好的选择。

好吧

  private volatile int invoc = 0;
我会成功的

并查看一些相关的java定义。显然int可以,但double&long可能不行



编辑,添加。问题是,“查看invoc的正确值吗?”。什么是“正确值”?正如在时空连续体中一样,线程之间并不存在同时性。上面的一篇文章指出,该值最终将被刷新,而另一个线程将得到它。代码是“线程安全”的吗?我会说“是”,因为在这种情况下,它不会因为测序的变幻莫测而“行为不端”。

据我所知,代码应该是安全的。字节码可以重新排序,是的。但最终invoc应该再次与主线程同步。同步保证invoc正确递增,以便在某些寄存器中有一致的invoc表示。在某个时候,该值将被刷新,小测试将成功


这当然不好,我会同意我投票赞成的答案,并且会修复这样的代码,因为它有味道。但是考虑一下,我会认为它是安全的。

< P>理论上,读是可能的。Java内存模型中没有任何东西可以阻止这一点

实际上,这是极不可能发生的(在您的特定示例中)。问题是,JVM是否可以跨方法调用进行优化

read #1
method();
read #2
为了让JVM推断read#2可以重用read#1的结果(可以存储在CPU寄存器中),它必须确定
method()
不包含任何同步操作。这通常是不可能的-除非,
method()
是内联的,并且JVM可以从扁平化的代码中看出,在读取#1和读取#2之间没有同步/易失性或其他同步操作;这样就可以安全地消除读取2

现在在您的示例中,方法是
Thread.sleep()
。实现它的一种方法是根据CPU频率在特定时间内进行忙循环。然后JVM可以内联它,然后消除读2

当然,这种
sleep()
的实现是不现实的。它通常作为调用OS内核的本机方法实现。问题是,JVM能否跨这种本机方法进行优化

即使JVM知道一些本机方法的内部工作原理,因此可以跨它们进行优化,
sleep()<代码>睡眠(1ms)
需要数百万个CPU周期才能返回,因此没有必要对其进行优化以节省一些读取

--

这一讨论揭示了数据竞争的最大问题——这需要付出太多的努力