2014-03-06 58 views
0

我讀取this文章聲稱使用靜態成員與java 1.5是線程安全的。這是否也意味着只有成員的一個實例或不同的線程可以緩存不同的實例?使用靜態易失性與靜態相關的實例數

例如:

public class MyClass { 
private static Foo foo = null; 

public String getFoo() 
{ 
    if (foo== null) 
     foo= new Foo(); 
    //init foo... 
    } 
} 

如果我有幾個線程做:

String foo = (new MyClass()).getFoo(); 

我想所有的線程會得到同樣的「初始化」富不過多少次foo將被初始化?以及如果我將添加volatile並將foo定義爲「public static volatile Foo ...」會改變什麼?

+0

在'Foo'的構造函數中放入一個日誌語句並嘗試。 –

回答

1

每個線程都可以看到null,每個線程都可以創建它自己的實例。使用volatile使這個可能性更小,但它仍然可以發生。

你需要做的是使用一些同步或靜態初始化(這是線程安全的)

public class MyClass { 
private static final Foo foo = new Foo(); 

public static String getFoo() { return foo; } 
} 

public class MyClass { 
private static Foo foo = null; 

public static synchronized String getFoo() 
{ 
    if (foo== null) 
     foo= new Foo(); 
    //init foo... 
    } 
} 

這是Java 5.0和所有其他版本爲真好。

+0

你的鎖的粒度超過需要 - 這就是爲什麼雙重檢查鎖通常用於這種情況。 – dcastro

+1

@dcastro在99%的情況下,雙重檢查鎖定比您需要的要複雜得多,因此幾乎總是有一種更好的方法來避免這樣做。 –

2

我讀過這篇文章,聲稱使用靜態成員與java 1.5是線程安全的。

更具體地講,在Java Memory Model FAQ特別說:

如果某個字段在靜態初始化設置,可以保證它是可見的,正確的,以訪問任何線程該類

在您發佈的代碼,你static字段是靜態初始化集。如果做了以下那麼就不會有一個線程安全問題:

private static Foo foo = new Foo(); 

然而,在其他大多數情況下與static領域的線程安全性問題:

換句話說,不要將在另一個線程可能能夠看到的地方引用正在構建的對象;請勿將其分配給靜態字段,請勿將其註冊爲任何其他對象的偵聽器,依此類推。

你的其他問題:

我想所有的線程會得到同樣的 「初始化」 富

沒有關於保證。兩個線程可以同時調用getFoo(),肯定會得到Foo的不同實例。

但是foo會被初始化多少次?

這是未知的,取決於有多少線程在運行,數據發佈等。

如果我將添加揮發性和foo定義爲「公共靜態揮發富......」它會改變什麼嗎?

這將確保至少Foo會當它被分配到靜態字段,但它絕不再保護你當多個線程在同一時間撥打getFoo()這種情況發生的競態條件妥善建造。

通常,當Foo的構造便宜時,我將使用volatile static字段模式,並且我不介意其中一個線程使用其實例Foo,然後進行垃圾回收。我只希望剩下的大部分操作都使用同一個實例。

另一種模式是做類似如下:

private static final AtomicReference<Foo> fooRef = new AtomicReference<Foo>(); 

public static String getFoo() { 
    Foo foo = fooRef.get(); 
    if (foo != null) { 
     return foo; 
    } 
    foo = new Foo(); 
    if (fooRef.compareAndSet(null, foo)) { 
     return foo; 
    } else { 
     // foo ref was set by another thread so our Foo is not used 
     return fooRef.get(); 
    } 
} 

這可能會創建,然後扔掉的Foo一對額外的實例,但至少所有線程將使用的Foo相同的實例。

但是,如果必須有且僅有一個Foo實例,並且您不能在靜態初始化程序中構建它(請參閱上文),那麼您將被迫鎖定其構造。