2015-02-06 83 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