【问题标题】:Thread-safe Enum Singleton线程安全枚举单例
【发布时间】:2015-02-06 15:22:14
【问题描述】:

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

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

【问题讨论】:

  • “我知道枚举方法不是线程安全的” - 好吧,前提是你使它们不安全。枚举通常是无状态的,此时它们是完全线程安全的。
  • 在这里使用枚举对我来说似乎很奇怪。它们是用来列举一些东西的,所以这是在滥用它。
  • @IngoBürk 使用带有单个实例的枚举作为单例是一种非常常见的模式。事实上,枚举在 JVM 中只创建一次,因此非常适合表示单例。有关该主题的更多信息,请参阅 Josh Bloch 的“Effective Java”。
  • @IngoBürk 对于枚举单例,请参阅stackoverflow.com/questions/70689/…
  • @JonSkeet 基于此stackoverflow.com/questions/2531873/… 我认为枚举方法不是线程安全的。我错过了什么?哇,乔恩你在升c方面也很出色。我一直想成为多种语言的专家,但行业趋势总是推动坚持使用一种语言。你是怎么做到的?

标签: java multithreading enums thread-safety singleton


【解决方案1】:

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

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

现在您可以通过这种方式访问​​您的单例方法:

int someField = Blah.INSTANCE.getSomeField();

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

就线程安全而言,我个人更喜欢原子变量而不是 volatile,例如,

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

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

如果您只需要您编写的操作,那么 volatile 变量应该可以工作。与 volatile 对应的变量相比,原子变量提供了更多操作,例如,compareAndSet 用于 Java 7,getAndUpdateupdateAndGet 用于 Java 8。有关讨论,请参阅 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,分别了解有关并发和单例模式的更多信息。

【讨论】:

  • AtomicReference 和 AtomicInteger 完全等同于 volatile 等价物,除非您需要比较和设置功能(该示例没有)。两个版本都不需要同步。
  • @jtahlborn 是的,但从一开始就使用原子变量并没有什么坏处(就性能而言),并且可以在以后轻松添加该功能。我通常也觉得与 volatile 变量相比,原子变量更易于使用和理解。
  • 您的回答暗示,当没有使用原子而不是 volatile 时,线程安全性会有所提高。
  • @jtahlborn 我相信这是因为它更容易推理原子并且它们允许更复杂的操作,例如您注意到的比较和设置。不过,我会更新我的答案,以反映这更像是个人喜好。
  • @GiovanniBotta,对a.set(...) 的调用,其中a 指的是AtomicXxxxxx,与更新volatile 变量具有完全相同的同步结果,而对a.get() 的调用具有完全相同的同步结果阅读volatile 的后果,那么关于它们的“推理”有何不同?
猜你喜欢
  • 1970-01-01
  • 2014-08-13
  • 1970-01-01
  • 1970-01-01
  • 2010-10-05
  • 1970-01-01
  • 1970-01-01
  • 2014-09-09
  • 2012-10-17
相关资源
最近更新 更多