2013-01-16 43 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。