线程安全的枚举单例

Thread-safe Enum Singleton

枚举很适合创建单例。我知道枚举方法不是线程安全的,所以我试图让它成为线程安全的。任何人都可以确认此实现是否正确。这么多地方用static和volatile好不好,能不能优化一下?由于内部 class 是私有的,所以我必须在枚举中创建函数来访问内部 class 功能。可以优化吗?

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

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

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 变量应该可以。原子变量提供了更多的操作,而不是它们的易失性对应物,例如 compareAndSet 用于 Java 7 和 getAndUpdateupdateAndGet 用于 Java 8。参见 this 进行讨论。

无论你如何声明它们 (atomic/volatile) 如果你的成员变量是线程安全的 并且 它们的线程安全策略是独立的你不需要担心线程单例中方法的安全性。例如,如果您需要一次性更新两个变量,那么您将不得不稍微重新考虑设计并引入适当的锁(在设置 获取它们的值时)。

请务必小心修改 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 以分别了解有关并发和单例模式的更多信息。