这个变量需要声明为volatile吗?

3

在这段代码中,MyThread类中的out变量需要声明为volatile吗?还是ThreadTest类中stdout变量的"可挥发性"会传递过来?

import java.io.PrintStream;

class MyThread implements Runnable
{
    int id;
    PrintStream out; // should this be declared volatile?

    MyThread(int id, PrintStream out) {
        this.id = id;
        this.out = out;
    }

    public void run() {
        try {
            Thread.currentThread().sleep((int)(1000 * Math.random()));
            out.println("Thread " + id);
        }
        catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class ThreadTest
{
    static volatile PrintStream stdout = System.out;

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(new MyThread(i, stdout)).start();
        }
    }
}
4个回答

2
在这段代码中,MyThread类中的out变量需要声明为volatile吗?还是ThreadTest类中的stdout变量所具有的“易失性”可以延续到MyThread类中?
实际上,易失性并不会“延续”,因为你实际上是传递变量的值。
根据我对JLS内存模型规范的阅读,如果你要在创建对象的线程和使用它的线程之间进行一些介入同步的操作,则需要使用volatile。
在现有的代码中,面临风险的变量是out。它是一个非私有变量,可以被任何访问该类的东西访问/更新。虽然你的示例中没有这样的代码,但你可以编写另一个类...或者改变ThreadTest类。
但在这种情况下,更好的解决方案是:
out声明为finalfinal字段的语义意味着不需要同步。
out声明为private。现在线程构造和start()调用之间的“happens-before”确保对out的唯一可能访问将看到正确的值。

2

volatile关键字不会保留,并且在上述代码中没有作用。在构造函数中初始化后永远不会改变的变量上进行读写时,volatile建立了一个内存屏障。出于同样的原因,在ThreadTest中使用volatile也没有作用。

需要明确的是,volatile适用于变量,而不是引用对象。


1
嗯...我对JLS内存模型规范的理解是,在构造函数中写入(非易失性,非final)字段并在另一个线程中读取它之间不存在“先行发生”关系。因此,在这个例子中,volatile限定符是非冗余的。 - Stephen C
@Stephen C,在构造函数返回之前,另一个线程无法访问变量是不可能的。 - Marcelo Cantos
1
除非对象被构造函数“不安全地发布”(在此情况下不是这种情况),否则不会出现问题。但在这里,我们谈论的是另一个线程在构造函数返回后访问变量,即在run方法中。 - Stephen C
对不起,Stephen,但我就是看不出来。你构造了对象,一次设置变量,永久不变,然后将其传递给另一个线程,该线程访问未更改且永远不会更改的同一变量。在这张图片中,哪里需要使用 volatile - Marcelo Cantos
@Marcelo - 问题在于JLS中没有说明构造函数会将变量内容刷新到主内存。因此,如果另一个线程在不同的处理器上执行,则在读取时可能会得到out的旧副本。这就是Java内存模型的全部内容。我强烈建议您阅读我的答案链接到的JLS章节。 - Stephen C
好的,我看了一下。17.4.5中的happens-before关系应该如下应用:hb(写入字段,将新对象传递给另一个线程)(第一条规则); hb(将对象传递给另一个线程,另一个线程接收对象)(第三条规则); hb(其他线程接收对象,其他线程读取字段)(第一条规则),因此hb(写入字段,其他线程读取字段)(第四条规则)。简而言之,通过任何类型的同步机制(队列、锁等)将新对象传递给另一个线程应该保证写操作发生在读操作之前。 - Marcelo Cantos

0

它肯定不会传递。但我不确定你想做什么。volatile修饰的是ThreadTest中的字段,而不是值。


0

不是必要的。

因为在调用 Thread.start 之前和之后创建的对象之间存在 先行发生关系(happens-before)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接