线程在没有volatile的情况下无法工作,并从RAM读取值而不是缓存

Volatile应该让线程从 RAM 中读取值,禁用线程缓存,如果没有volatile缓存,将启用线程不知道另一个线程所做的变量更改,但这不适用于以下代码。

为什么会发生这种情况,并且代码在使用和不使用volatile关键字时都一样?

public  class Racing{
    
     private  boolean won = false;  //without volatile keyword
      
     public void race() throws InterruptedException{
        Thread one = new Thread(()->{
            System.out.println("Player-1 is racing...");
            while(!won){
              won=true;
            }
            System.out.println("Player-1 has won...");
        });
        
        Thread two=new Thread(()->{
           System.out.println("Player-2 is racing...");
            while(!won){
               System.out.println("Player-2 Still Racing...");
           } 
        });
        
        one.start();
        //Thread.sleep(2000);
        two.start();
      }
      
      public static void main(String k[]) {
        Racing racing=new Racing();
        try{
             racing.race();
        }
        catch(InterruptedException ie){}
      }
  

为什么这在有和没有volatile 的情况下表现相同?

回答

Volatile 应该让线程从禁用线程缓存的 RAM 中读取值

不,这不准确。这取决于运行代码的架构。Java 语言标准本身并没有说明应该或不应该如何实现volatile

来自程序员相信关于 CPU 缓存的神话可以阅读:

作为一名在 Intel 和 Sun 从事缓存工作了五年的计算机工程师,我已经了解了一两件事关于缓存一致性。(...) 另一方面,如果 volatile 变量真正从主内存中写入/读取 > 每次,它们会非常慢——主内存引用比 L1 缓存引用慢 200 倍以上。实际上,易失性读取(在 Java 中)通常与 L1 缓存引用一样便宜,从而消除了易失性强制读取/写入一直到主内存的观念。如果您因为性能问题一直避免使用 volatile,那么您可能已经成为上述误解的受害者。

不幸的是,仍然有几篇在线文章宣传这种不准确性(volatile 会强制从主存储器读取变量)。

根据语言标准(第 17.4 节):

一个字段可能被声明为 volatile,在这种情况下,Java 内存模型确保所有线程看到变量的一致值

因此,非正式地,所有线程都将查看该变量的最新值。没有关于硬件应该如何实施这种约束。

为什么会发生这种情况,并且代码在使用和不使用 volatile 的情况下都相同

好吧(在您的情况下)没有volatile未定义的行为,这意味着可能会或不会看到 flag 的最新值won,因此,理论上竞争条件仍然存在。但是,因为您添加了以下语句

System.out.println("Player-2 Still Racing...");

在:

Thread two = new Thread(()->{
             System.out.println("Player-2 is racing...");
             while(!won){
                  System.out.println("Player-2 Still Racing...");
             }
});

会发生两件事,您将避免旋转字段问题,其次,如果查看System.out.println代码:

   public void println(String x) {
        synchronized (this) {
            print(x);
            newLine();
        }
    }

可以看到有一个synchronized被调用,这将增加线程读取该字段的最新值的可能性flag(在调用println方法之前)。但是,即使这可能会根据 JVM 实现而改变。


以上是线程在没有volatile的情况下无法工作,并从RAM读取值而不是缓存的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>