2015-02-06 84 views
3

枚舉是創建單身人士的好。我知道枚舉方法不是線程安全的,所以我試圖讓它是線程安全的。任何人都可以確認這個實現是否正確。使用靜態和易變的這麼多地方是否可以,並且可以優化?由於內部類是私有的,所以我必須在枚舉中創建函數來訪問內部類的功能。可以優化嗎?線程安全枚舉單身人士

import java.util.Date; 

public enum SingletonWithEnum { 
    INSTANCE; 

    private static class Singleton{ 

     private static volatile int count; 
     private static volatile Date date;  

     public static int getCount() { return count;} 

     public static void setCount(int countParam) { synchronized(Singleton.class){ count = countParam; }} 

     public static Date getDate() { return date;} 

     public static void setDate(Date dateParam) { synchronized(Singleton.class){ date = dateParam;}} 

     public static String printObject() { 
      return "Singleton [count=" + getCount() + ", date=" + getDate() + "]"; 
     } 

    } 

    public int getCount() { return Singleton.getCount();} 

    public void setCount(int countParam) {Singleton.setCount(countParam);} 

    public Date getDate() { return Singleton.getDate();} 

    public void setDate(Date dateParam) {Singleton.setDate(dateParam);} 

    public String toString(){return Singleton.printObject();} 
}; 

我正在像這樣使用它。

SingletonWithEnum object1 = SingletonWithEnum.INSTANCE; 
object1.setCount(5); 
object1.setDate(new Date()); 
+6

「我知道枚舉方法不是線程安全的」 - 好吧,只有當你*使它們不安全。枚舉通常是無狀態的,此時它們完全是線程安全的。 – 2015-02-06 15:25:52

+0

在這裏使用枚舉對我來說似乎很奇怪。他們打算列舉一些東西,所以這是濫用它。 – 2015-02-06 16:33:33

+3

@IngoBürk將單個實例的枚舉用作單例是一種非常常見的模式。實際上,枚舉在JVM中只創建一次,所以完美地代表單身人士。有關該主題的更多信息,請參閱Josh Bloch的「Effective Java」。 – 2015-02-06 16:44:56

回答

6

首先,你不需要在你的枚舉中嵌套的類。你只需要定義成員和方法在枚舉本身,即

enum Blah { 
    INSTANCE; 
    private int someField; 
    public int getSomeField() { return someField; } 
} 

現在您可以訪問你的單身方法是這樣的:

int someField = Blah.INSTANCE.getSomeField(); 

而且,使成員靜態是種反-pattern,因爲單例實例應該擁有它的成員。所以它們應該是實例變量,而不是靜態變量。只有一個單例實例確保您的JVM中每個成員只有一個實例。

至於線程安全性而言,我個人更喜歡波動,例如原子變量,

private final AtomicInteger count = new AtomicInteger(); 
private final AtomicReference<Date> date = new AtomicReference<>(new Date()); 

注意到他們必須是爲了真正線程安全的聲明final因爲原子變量自己不會改變,儘管它們的價值可能。

如果你只需要你編碼的操作,volatile變量應該可以工作。對於Java 8,原子變量提供一些更多的操作,而不是它們的易變對應物,例如compareAndSetgetAndUpdateupdateAndGet。參見this進行討論。

但是你聲明它們(原子/揮發性),如果你的成員變量是線程安全的他們線程安全的政策是獨立的,你不用擔心的方法在你的單身的纖維性安全。如果你需要例如一次更新兩個變量的原子,那麼你將不得不重新考慮這個設計,並引入適當的鎖定(當設置獲得它們的值時)。

對於修改Date對象的方式非常重要。 Date線程安全的,所以我強烈建議恢復副本,並用更改後,當副本替換實例,即(假設您如上使用AtomicReference),

public Date getDate() { return new Date(date.get().getTime()); } 
public void setDate(Date d) { 
    date.set(new Date(d.getTime())); 
} 

最後,我強烈推薦Brian Goetz的Concurrency in Practice和Joshua Bloch的Effective Java分別詳細瞭解併發和單例模式。

+2

AtomicReference和AtomicInteger完全等價於volatile等價物,除非您需要比較和設置功能(該示例沒有)。根本沒有版本需要同步。 – jtahlborn 2015-02-06 16:48:27

+0

@jtahlborn是真實的,但是從一開始就使用原子變量不會傷害(就性能而言),並且可以在稍後添加該功能。我通常也會覺得原子變量比易變的變量更易於使用和理解。 – 2015-02-06 16:52:31

+0

你的答案意味着當使用原子時,如果使用原子而不是易失性的,那麼線程安全就會有所增加。 – jtahlborn 2015-02-06 17:06:00