从内部类线程访问外部字段(非最终)是否是线程安全的?

时间:2013-01-16 09:06:20

标签: java multithreading thread-safety

基本上以下工作但是因为我读到了关于final关键字,如果不同的线程访问它我是否必须声明name final,我不知道了吗?

提前致谢。

public class Test4 {

    // to ensure thread-safety do we have to declare the variable name final ?
    private String name;

    public Test4 (String name) {
        this.name = name;
    }

    public void start() {
        new MyThread().start();
    }

    private class MyThread extends Thread {

        public void run() {
            System.out.println(name);
        }
    }

    public static void main(String[] args) {
        Test4 t = new Test4("Don't know if I am threadsafe");
        t.start();
    }

}

6 个答案:

答案 0 :(得分:4)

final修饰符 - 同时阻止重新分配成员 - 影响给定代码 1的正确性

来自Java 5语言规范的17.4.4 Synchronization Order部分:

  

同步顺序是执行的所有同步操作的总顺序。同步操作会引发与操作同步的关系,定义如下:

     
      
  • ..
  •   
  • 启动线程的操作与启动的线程中的第一个操作同步
  •   
  • ..
  •   

然后,由于设置 name成员的线程是启动线程的线程,因此保证了同步顺序。 (同步 - 表示Happens-before ordering。)

请注意:

  • 成员name只需要在启动线程之前设置:也就是说,不需要在构造函数中设置此同步保证。
  • 保证同步排序 - 因此保证在已经运行的线程或在其他地方创建的线程之间发生之前或值可见性!

但是,final字段确实给人一种更舒适的感觉(参考17.5 Final Field Semantics):

  

当构造函数完成时,对象被认为是完全初始化的。在该对象完全初始化之后只能看到对象*的引用的线程保证看到该对象的最终字段的正确初始化值

在这种情况下,使用final字段,保证在构造函数完成后在每个线程上显示该值。 ("constructor leaks"可以违反此保证。)


1 在提供的代码中,“非最终”name成员仅在线程启动之前分配一次。

不同的中,可能会暴露其他同步问题。此答案检查是否删除final是否会改变所提供代码的正确性。

所有这些,我认为使用不可变变量(final)和不可变对象是“好习惯” - 尤其是在处理线程时。不需要知道JVM的小奥秘细节,而是做出安全可靠的事情,并且在聪明或“表现”方面力求明显的正确性

另见:

答案 1 :(得分:3)

最终变量是immutable,一旦构造它就不能更改它,因此它没有并发问题。

答案 2 :(得分:2)

没有最终版本,您将无法获得该字段的正确值。

更改字段值后,线程可能会获得旧值。

检查JMM的Visibility

Another link of volatile

Happends-Before Rule

Happends-Before in JMM

答案 3 :(得分:1)

您是否正在寻找AtomicReferencevolatile?这取决于你的意思线程安全

// Atomic to allow deeper control of updates.
private AtomicReference<String> name = new AtomicReference<String>();
// Volatile to ensure it is not cached.
private volatile String vName;

public Test(String name) {
  this.name.set(name);
  this.vName = name;
}

public void start() {
  new MyThread().start();
}

private class MyThread extends Thread {
  public void run() {
    System.out.println(name.get());
    System.out.println(vName);
  }
}

答案 4 :(得分:0)

final与多线程没有任何关系,但是如果你的fild不应该更改并且在类的构造函数中初始化,你应该放final。这意味着后者不能改变fild。

答案 5 :(得分:0)

因为String是不可变的,你声明字段final,所有线程在字段赋值后访问它,然后肯定不会有并发问题,因为该字段仅用于Read操作,与StringBuilder的情况相反被使用了。