2015-02-23 88 views
0

只是爲了確保我理解java實踐中呈現的概念。java中的初始化安全性

可以說我有以下程序:

public class Stuff{ 
    private int x; 

    public Stuff(int x){ 
     this.x=x; 
    } 

    public int getX(){return x;} 
} 

public class UseStuff(){ 
    private Stuff s; 

    public void makeStuff(int x){ 
     s=new Stuff(x); 
    } 

    public int useStuff(){ 
     return s.getX(); 
    } 
} 

如果我讓多個線程使用此代碼打球,那麼我不僅麻煩,因爲S可能會發生被指向如果兩個或更多的多個實例線程正在進入makeStuff方法,但即使只有一個線程創建了一個新的Stuff,那麼剛剛進入useStuff的其他線程可以通過其構造函數返回值0(預定義的int值)或分配給「x」的值。

這一切都取決於構造函數是否已完成初始化x。

所以在這一點上,爲了使線程安全,我必須做一件事,然後我可以從兩種不同的方式中選擇。

首先,我必須make makeStuff()原子,所以「s」將一次指向一個對象。

然後,我要麼使useStuff同步,以確保我只能在其構造函數完成構建後才能返回Stuff對象x var,或者我可以使Stuff的x最終,並由此JMM確保x的值只有在初始化後纔可見。

我瞭解最終字段在併發和JMM上下文中的重要性嗎?

+0

假設UseStuff的實例在線程之間共享,那麼:no,s將始終指向一次只有一個實例。你的問題是你不確定它是哪個實例,因爲多個線程可能會覆蓋該引用。通過將其設置爲final,您可以確保在UseStuff初始化時設置了引用s,並且在任何線程中都不會更改它。 – Gergely 2015-02-23 21:23:04

+0

對不起,只是注意到你想做x最後。所以在這種情況下,你要保證一旦創建了一個Stuff實例,它的字段x就再也不會被改變了。在這種情況下,字段仍然可以被新的Stuff覆蓋,但是至少可以確定Stuff的特定實例永遠不會有任何線程更改其x。 – Gergely 2015-02-23 21:29:23

回答

0

你想通過使事物同步來保護你有什麼?你是否擔心線程A會調用makeStuff,然後線程B會調用getStuff並且值不會在那裏?我不知道如何同步任何這將有助於這一點。根據你想要避免什麼問題,它可能就像標記爲volatile一樣簡單。

-1

我不知道你在那裏做什麼。你爲什麼要創建一個對象,然後將其分配給一個字段?爲什麼要保存它,如果它可以被其他電話覆蓋makeStuff?它看起來像你使用UseStuff作爲代理,並作爲你的實際Stuff模型對象的工廠。您更好地分離兩個:

public class StuffFactory { 
    public static Stuff createStuff(int value) { 
    return new StuffProxy(value); 
    } 
} 

public class StuffProxy extends Stuff { 
    // Replacement for useStuff from your original UseStuff class 
    @Override 
    public int getX() { 
    //Put custom logic here 
    return super.getX(); 
    } 
} 

這裏的邏輯是,每個線程負責創建他們自己的東西的對象(使用工廠),所以併發訪問不再是一個問題。

3

我瞭解最終字段在併發和JMM環境中的重要性嗎?

不完全。該規範writes

final領域也允許程序員來實現線程安全的不可變對象不同步。線程安全的不可變對象被所有線程看作是不可變的,即使使用數據競爭將線程間不可變對象的引用傳遞給線程。這可以通過不正確或惡意代碼

提供針對不可變類的誤用的安全保證如果您x最終,這保證了每一個獲得一個Stuff實例的引用線程將觀察x已被分配。它並不保證任何線程都會獲得這樣的參考。

也就是說,在缺少useStuff()中的同步操作時,允許運行時從寄存器中滿足讀取s,該寄存器可能會返回一個陳舊值。

此代碼的最便宜的正確同步變體聲明爲s易失性,這可確保寫入s發生在之後(因此可見)後續讀取s。如果你這樣做,你甚至不需要做最終的x(因爲寫入x發生 - 在寫入s之前,s的讀取發生在讀取x之前,並且發生在傳遞之前)。

2

一些答案聲稱s一次只能引用一個對象。這是錯誤的;因爲沒有內存屏障,不同的線程可以對s的值有自己的概念。爲了讓所有線程看到分配給s的一致值,您需要聲明svolatile,或者使用其他一些內存屏障。

如果你這樣做,你就不會需要聲明x作爲final正確的價值在於爲所有線程都是可見的(但你可能還是想;領域不應該是沒有原因的可變)。這是因爲x初始化之前發生s在分配「源代碼順序,」和揮發性場s的寫操作之前發生其他線程s讀取值。但是,如果您隨後修改了非最終字段x的值,則可能會遇到麻煩,因爲修改不能保證對其他線程可見。使不可變的Stuff將消除這種可能性。

當然,沒有什麼可以阻止線程將0x123的值分配給s,因此不同的線程仍然可以看到x的不同值。但這不是一個真正的線程問題。即使是單個線程也可以隨時寫入然後讀取不同的值x。但是,在多線程環境中阻止這種行爲需要原子性,即檢查以查看s是否有值,如果沒有,則分配一個值應該顯示爲其他線程的一個不可分割操作。一個AtomicReference將是最好的解決方案,但​​關鍵字也可以。

+0

說某個特定時間s可以有多個值可能是正確的,但這種思考方式不會幫助您編寫正確的代碼。你如何證明線程X正在讀取「正確的」值?想一想更好的方法是,變量一次只有一個值,但很難知道它是什麼時間。Java的規則是這樣的:「發生之前」的規則告訴你如何正確地同步程序,以便當某些給定的線程執行某些特定的代碼行時,可以提示它是什麼時間(相對於其他線程中的事件)。 – 2015-02-23 22:15:50

+0

@jameslarge這是一個相當的方式來看待它。我不明白它有多好。去任何有意義的東西給你。 – erickson 2015-02-23 23:51:46

+0

這更好,因爲它是從Java的「發生之前」規則寫入的角度。 「之前發生」規則討論的線程可能會或可能不會以相同的順序經歷相同的字段更新,並且它們告訴我們如何在重要時限制重新排序(即如何「同步」)。如果您選擇實時查看實際發生的內容,則必須調用「緩存」,「刷新」和「無效」等單詞。這個觀點有助於你看到問題,但這與幫助你解決問題的正式規則是相悖的。 – 2015-02-24 18:29:25