使用volatile关键字

时间:2010-04-15 11:23:58

标签: java volatile

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

但是在下面的程序中,volatile和non-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

6 个答案:

答案 0 :(得分:7)

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

如果您声明它们static,它们将在不同的线程之间共享。但是,即使这样,您也可能无法观察到易失性和非易失性变量之间的差异。引自Java Concurrency in Practice,第二章。 3.1.4:

  

volatile变量的可见性效果超出了volatile变量本身的值。当线程A写入易失性变量并且随后线程B读取相同的变量时,在写入易失性变量之前,A可见的所有变量的值在读取volatile变量后变为B可见。因此,从内存可见性的角度来看,编写volatile变量就像退出synchronized块一样,读取volatile变量就像进入synchronized块一样。

在您的情况下,代码首先修改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-construct 状态 - 如果对象是可变的,则读者和编写者仍然需要同步以使后续修改可见并避免数据损坏。

答案 1 :(得分:3)

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

答案 2 :(得分:2)

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

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

第二,你没有给int字段分配任何东西,所以你得到0。

答案 3 :(得分:1)

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

使testValue静态和volatile会产生影响。但是,您可能不会(也可能不会)看到这种效果,因为它高度依赖于您(甚至是VM)控件之外的时序,调度和缓存策略。丢失的volatile只会很少产生影响,这使得这些bug很难捕获。只有当一个线程更新该值并且第二个线程读取该值并且该值仍在两个线程中的任何一个的缓存中时,才会看到它。

答案 4 :(得分:1)

您可能丢失静态关键字?

答案 5 :(得分: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线程可能“无限期地”运行。