【问题标题】:using volatile keyword使用 volatile 关键字
【发布时间】:2011-02-08 08:12:20
【问题描述】:

据我了解,如果我们将变量声明为 volatile,那么它将不会存储在本地缓存中。每当线程更新值时,它都会更新到主内存。因此,其他线程可以访问更新的值。

但在以下程序中,易失性和非易失性变量都显示相同的值。

没有为第二个线程更新 volatile 变量。 谁能解释一下为什么 testValue 没有改变。

class ExampleThread extends Thread {
    private int testValue1;
    private volatile int testValue;
    public ExampleThread(String str){
      super(str);
    }
    public void run() {
    if (getName().equals("Thread 1 "))
    {
        testValue = 10;
        testValue1= 10;
        System.out.println( "Thread 1 testValue1 : " + testValue1);
        System.out.println( "Thread 1 testValue : " + testValue);
    }
    if (getName().equals("Thread 2 "))
    {
        System.out.println( "Thread 2 testValue1 : " + testValue1);
        System.out.println( "Thread 2 testValue : " + testValue);
    }               
}
}

public class VolatileExample {
    public static void main(String args[]) {
        new ExampleThread("Thread 1 ").start();
        new ExampleThread("Thread 2 ").start();
    }
}


output:
Thread 1 testValue1 : 10
Thread 1 testValue : 10
Thread 2 testValue1 : 0
Thread 2 testValue : 0

【问题讨论】:

    标签: java volatile


    【解决方案1】:

    这是一个示例,显示了一个由两个线程访问的变量。 StarterThread 线程在线程启动时设置变量startedWaiterThread 等待设置变量 started

    public class Main
    {
        static /*volatile*/ boolean started = false;
    
        private static class StarterThread extends Thread
        {
            public void run()
            {
                started = true;
            }
        }
    
        private static class WaiterThread extends Thread
        {
            public void run()
            {
                while (!started)
                {
                }
            }
        }
    
        public static void main(String[] args)
        {
            new StarterThread().start();
            new WaiterThread().start();
        }
    }
    

    如果started 不是易失性的,则没有同步点可以保证WaiterThread 将永远获得started 变量的更新值。因此,WaiterThread 线程可能会“无限期地”运行。

    【讨论】:

      【解决方案2】:

      您的变量仅限于单个线程,因此没有其他线程访问它们。因此volatile 没有区别。

      如果您声明它们static,它们将在不同线程之间共享。但是,即使那样,您也可能无法观察到 volatile 和 nonvolatile 变量之间的区别。引用Java Concurrency in Practice,第一章。 3.1.4:

      volatile 变量的可见性影响超出了 volatile 变量本身的值。当线程 A 写入 volatile 变量并且随后线程 B 读取相同的变量时,在写入 volatile 变量之前对 A 可见的所有变量的值在读取 volatile 变量后对 B 可见。所以从内存可见性的角度来看,写一个 volatile 变量就像退出一个同步块,读一个 volatile 变量就像进入一个同步块。

      在您的情况下,代码恰好先修改了 volatile 变量,因此其他变量的更新值可能对其他线程不可见。到目前为止,一切顺利。

      但是,由于您是从修改它们的同一线程打印出变量的值,所以无论如何您都不会看到任何差异。

      Update2:试试这个修改后的版本(注意:我没有测试过):

      class ExampleThread extends Thread {
          private static int testValue1;
          private static volatile int testValue;
          private int newValue;
      
          public ExampleThread(String str, int newValue){
            super(str);
            this.newValue = newValue;
          }
          public void run() {
            for (int i = 0; i < 10; i++) {
              System.out.println(getName() + " testValue1 before update: " + testValue1);
              System.out.println(getName() + " testValue before update: " + testValue);
              testValue = i * newValue;
              testValue1 = i * newValue;
              System.out.println(getName() + " testValue1 after update: " + testValue1);
              System.out.println(getName() + " testValue after update: " + testValue);
              sleep(10);
            }               
          }               
      }
      
      public class VolatileExample {
          public static void main(String args[]) {
              new ExampleThread("Thread 1 ", 5).start();
              new ExampleThread("Thread 2 ", 10).start();
          }
      }
      

      更新:关于静态字段的可见性 - 再次来自同一部著作(第 16.2.3 章):

      [...] 静态初始化的对象在构造期间或被引用时不需要显式同步。但是,这仅适用于 as-constructed 状态 - 如果对象是可变的,读取器和写入器仍需要同步以使后续修改可见并避免数据损坏。

      【讨论】:

      • 我问的是易失性,而不是静态...不管有没有易失性,静态都可以正常工作
      • 您可能想澄清一下,您的短语“仅限于单个线程”是指线程实例,而不是线程本身(我们可能会使用 ThreadLocal 将变量限制在线程本身)。
      • @Steven Mackenzie 在上面的例子中,有问题的变量是私有成员,只能由它们的所有者访问(恰好是一个线程实例)。因此,它们显然仅限于与该实例关联的线程。 ThreadLocal 并不是实现线程限制的唯一方式。
      【解决方案3】:

      testValue是一个成员变量,所以两个线程看到两个独立的副本。当两个或多个线程引用同一个对象时,volatile 是相关的。

      使 testValue 静态和易失会产生影响。但是,您可能不会(也可能不会)看到这种效果,因为它高度依赖于您(甚至虚拟机)无法控制的时间、调度和缓存策略。缺少的 volatile 很少会产生影响,这使得此类错误很难捕获。只有当一个线程更新值并且第二个线程读取该值并且该值仍在两个线程中的任何一个的缓存中时才会看到。

      【讨论】:

      • 它实际上是一个成员变量,而不是一个局部变量。查看多基因的答案。
      • 你能用例子解释一下吗...我没有得到
      • @Steven 你是对的。更新了我的答案。 @sap 查看我的更新答案
      【解决方案4】:

      这与volatile无关;它们是ExampleThread 的两个独立实例,它们有自己的testValue1testValue 副本,它们是实例字段(不是static 类变量,它们在所有实例之间“共享”)。

      【讨论】:

      • 你能解释一下易失性功能
      • 您应该阅读 Java 内存模型和“Java 并发实践”一书。
      【解决方案5】:

      ExampleThread 1 和 ExampleThread 2 是不同的对象。

      在其中一个中,您为两个 int 字段分配了 10,这就是为什么您会看到第一个线程的输出。

      在第二个中,您没有为 int 字段分配任何内容,因此您得到 0。

      【讨论】:

      • 你可以修改一下我们如何为 volatile 实现它
      【解决方案6】:

      您可能丢失了静态关键字吗?

      【讨论】:

      • 我问的是易失性,而不是静态...不管有没有易失性,静态都可以正常工作
      猜你喜欢
      • 1970-01-01
      • 2016-02-12
      • 1970-01-01
      • 2013-08-06
      • 1970-01-01
      • 1970-01-01
      • 2015-02-16
      • 2020-12-18
      • 1970-01-01
      相关资源
      最近更新 更多