4

据我了解,如果我们将变量声明为 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
4

6 回答 6

7

您的变量仅限于单个线程,因此没有其他线程访问它们。因此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 章):

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

于 2010-04-15T11:30:34.923 回答
3

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

于 2010-04-15T11:35:09.377 回答
2

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

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

其次,您没有为 int 字段分配任何内容,因此您得到 0。

于 2010-04-15T11:31:28.507 回答
1

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

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

于 2010-04-15T11:29:16.370 回答
1

可能是你丢失了静态关键字?

于 2010-04-15T11:29:26.337 回答
1

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

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线程可能会“无限期地”运行。

于 2010-04-15T12:23:18.870 回答