线程安全的Enum Singleton

时间:2015-02-06 15:22:14

标签: java multithreading enums thread-safety singleton

枚举有助于创建单身人士。我知道枚举方法不是线程安全的,所以我试着让它成为线程安全的。任何人都可以确认这种实施是否正确。是否可以使用静态和易失性这么多地方并且可以进行优化?由于内部类是私有的,因此我必须在枚举中创建函数以访问内部类功能。可以优化吗?

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());

1 个答案:

答案 0 :(得分:8)

首先,您的枚举中不需要嵌套类。您只需要在枚举本身中定义成员和方法,即

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

现在你可以这样访问你的单例方法:

int someField = Blah.INSTANCE.getSomeField();

此外,使成员静态在这里是一种反模式,因为单例实例应该拥有其成员。所以它们应该是实例变量,而不是静态变量。事实上,只有一个单例实例确保您的JVM中只有一个每个成员的实例。

就线程安全而言,我个人更喜欢原子变量而非易失性,例如,

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

请注意,必须才能声明为final才能真正保证线程安全,因为原子变量本身不会改变,尽管它们的值可以。

如果您只需要编码的操作,那么volatile变量应该有效。与易失性对应物相比,原子变量提供了更多操作,例如,Java 7为compareAndSet,Java 8为getAndUpdateupdateAndGet。请参阅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,分别了解有关并发和单例模式的更多信息。