2013-01-16 41 views
3

基本上以下的作品,但自从我读了关于最后的关键字我不知道了,如果我不得不声明名称最终如果不同的线程访问它?从内部类的线程访问外部字段(非最终)是否是线程安全的?

在此先感谢。

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(); 
    } 

} 
+0

这将永远打印“不知道我是否是线程安全的”,无论最终如何。在构造对象之前,new()不会返回;这将在新线程启动之前发生。 – tucuxi

+0

@pst在进一步写入'name'的情况下,是的,有差异。但是,如果写'唯一时间'是在施工期间,那么'最终'是不需要的(尽管它确实使读者更清楚)。 – tucuxi

回答

3

最终的变量是immutable,一旦构成,因此不会有并发问题,你不能改变的值。

+0

尽管如此,最终变量评估为可变对象最终会出现在奇怪行为的世界中。所以在这种情况下,它是真实的,因为*变量是final *并且String对象是不可变的。 (然而,为了回答它是否是线程安全的问题,需要知道发生 - 在非静态final * member *字段之前,并且我不完全确定最终 - 或者缺少 - 实际上改变了这种行为。 ) – 2013-01-16 09:22:22

+0

所以'final'确实对构造函数和线程做了一些事情(参见[Final Field Semantics](http://docs.oracle.com/javase/specs/jls/se5.0/html/memory.html#66562) );这是通过保证JVM语义,但*不*只是“因为它不能被重新分配”。 – 2013-01-16 09:45:09

+0

感谢您的语义,我同意最终变量的完整初始化的“使用模型”。 –

2

如果没有最终结果,您将无法获得该字段的正确值。

也许线程在更改字段值后得到了旧值。

检查JMM的Visibility

Another link of volatile

Happends-Before Rule

Happends-Before in JMM

+1

我不相信这是正确的。也就是说,在这种情况下,不会启动线程导致“发生之前”的线程? (虽然目前我找不到支持信息。) – 2013-01-16 09:13:20

+0

是的,你的权利。我重写了答案。 – imxylz

+0

啊,我想在这里:http://stackoverflow.com/questions/7651226/java-happend-before-thread-start(但我可能会读错) – 2013-01-16 09:19:28

0

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

+0

'最后'确实增加了多线程保证,看来;在这种情况下,在构造函数存在之后,最终的字段值被*保证*为可见的(由其他线程)。 – 2013-01-16 09:46:58

+0

这个保证不会被忽视,因为假设的其他线程可能会或可能不会完成执行构造函数开始执行时读取“final”字段的部分。所以这种特殊的竞争条件仍然存在。 – tucuxi

1

您是否在寻找AtomicReference或者volatile?这取决于你的意思是线程安全

// 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

final改性剂 - 同时防止构件被重新分配 - 影响给出代码

从所述17.4.4 Synchronization Order部分的正确性为Java 5语言规范:

一个同步订单是一个总数为的订单执行的同步操作 ..同步动作诱导同步-与动作关系,定义如下:

  • ..
  • 这个动作开始线程同步-与第一行动在线程开始
  • ..

然后,由于线程name构件是开始螺纹中的一个,则同步顺序有保证。 (同步 - 暗示Happens-before ordering。)

需要注意的是:

  • name仅需要之前启动线程设置员:那就是,它并不需要在构造函数中设置此同步,有保证。
  • 这是的不是保证同步排序 - 因此它确实而不是保证发生之前或值可见性 - 之间已经运行的线程或线程之间创建的线程!

然而,final领域做给一个更加舒适的感觉(REF 17.5 Final Field Semantics):

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

在这种情况下,最后的字段,该值保证是可见线构造完成后。 (这保证可以通过"constructor leaks"被侵犯。)


在提供的代码线程开始之前的“非最终” name构件仅被分配一次

不同,不那么微不足道,程序可能会暴露其他同步问题。此答案检查是否删除final会改变所提供的代码的正确性。我认为使用不可变变量(final)和不可变对象(尤其是在处理线程时)是“良好实践”。不需要知道JVM的神秘细节,安全的事情和争取在聪明或“性能”方面明显的正确性

参见:

+0

过于复杂的答案(注意对象的start()方法仅在构建它之后被调用,因此将看到完全构建的状态,将是足够的),但是正确且完整。 – tucuxi

0

因为字符串是不可改变的,声明场决赛中,所有线程的字段赋值后访问它,然后当然,不会出现并发问题,因为该字段仅用于读取操作,与Strin的情况相反使用了gBuilder。