Java中的单例和multithreading

在multithreading环境中使用Singleton类的首选方法是什么?

假设我有3个线程,并且所有这些线程都试图同时访问singleton类的getInstance()方法 –

  1. 如果不保持同步会发生什么?
  2. 使用synchronized getInstance()方法或在getInstance()使用synchronized块是一种好习惯。

请告知是否还有其他方法。

理论上,这项任务非常重要,因为您希望使其真正具有线程安全性。

@ IBM发现了一篇关于此事的非常好的论文

刚刚获得单例不需要任何同步,因为它只是一个读取。 因此,只需同步同步设置即可。 除非两个步骤尝试在启动时同时创建单例,否则您需要确保检查实例是否设置了两次(一个在同一个外部,一个在同步内),以避免在最坏的情况下重置实例。

然后,您可能需要考虑JIT编译器如何处理无序写入。 这段代码有点接近解决方案,但无论如何都不是100%线程安全的:

 public static Singleton getInstance() { if (instance == null) { synchronized(Singleton.class) { Singleton inst = instance; if (inst == null) { synchronized(Singleton.class) { instance = new Singleton(); } } } } return instance; } 

所以,你应该采取一些不那么懒惰的东西:

 class Singleton { private static Singleton instance = new Singleton(); private Singleton() { } public static Singleton getInstance() { return instance; } } 

或者,更加臃肿,但更灵活的方法是避免使用静态单例并使用Spring等注入框架来管理“singleton-ish”对象的实例化(并且可以配置延迟初始化)。

如果你正在谈论线程安全 , 懒惰的单例 初始化 ,这里是一个很酷的代码模式,可以完成100%线程安全的延迟初始化而不需要任何同步代码

 public class MySingleton { private static class MyWrapper { static MySingleton INSTANCE = new MySingleton(); } private MySingleton () {} public static MySingleton getInstance() { return MyWrapper.INSTANCE; } } 

这将仅在调用getInstance()时实例化单例,并且它是100%线程安全的! 这是经典之作。

它的工作原理是因为类加载器有自己的同步来处理类的静态初始化:保证在使用类之前所有静态初始化都已完成,并且在此代码中,类仅在getInstance()方法中使用,因此当类加载加载内部类时。

@Singleton说一句,我期待有一个@Singleton注释存在处理这些问题的那一天。

编辑:

一个特别不相信的人声称包装类“什么都不做”。 这是certificate它确实很重要,尽管在特殊情况下。

基本的不同之处在于,使用包装器类版本时,单例实例是在加载包装器类时创建的,当第一次调用getInstance()会创建一个非包装版本 – 即一个简单的静态初始化 -在加载主类时创建实例。

如果你只是简单地调用了getInstance()方法,那么几乎没有区别 – 不同之处在于,在使用包装版本创建实例之前 ,所有其他 sttic初始化都已完成,但这很容易通过只需在源代码中最后列出静态实例变量。

但是,如果您按名称加载类,则故事情况则完全不同。 在一个类上调用Class.forName(className)来进行静态初始化,所以如果要使用的单例类是服务器的属性,那么在调用Class.forName(className)时,将使用简单版本创建静态实例, 不是在调用getInstance()时。 我承认这有点人为,因为你需要使用reflection来获取实例,但是这里有一些完整的工作代码来演示我的争用(以下每个类都是顶级类):

 public abstract class BaseSingleton { private long createdAt = System.currentTimeMillis(); public String toString() { return getClass().getSimpleName() + " was created " + (System.currentTimeMillis() - createdAt) + " ms ago"; } } public class EagerSingleton extends BaseSingleton { private static final EagerSingleton INSTANCE = new EagerSingleton(); public static EagerSingleton getInstance() { return INSTANCE; } } public class LazySingleton extends BaseSingleton { private static class Loader { static final LazySingleton INSTANCE = new LazySingleton(); } public static LazySingleton getInstance() { return Loader.INSTANCE; } } 

主要:

 public static void main(String[] args) throws Exception { // Load the class - assume the name comes from a system property etc Class lazyClazz = (Class) Class.forName("com.mypackage.LazySingleton"); Class eagerClazz = (Class) Class.forName("com.mypackage.EagerSingleton"); Thread.sleep(1000); // Introduce some delay between loading class and calling getInstance() // Invoke the getInstace method on the class BaseSingleton lazySingleton = (BaseSingleton) lazyClazz.getMethod("getInstance").invoke(lazyClazz); BaseSingleton eagerSingleton = (BaseSingleton) eagerClazz.getMethod("getInstance").invoke(eagerClazz); System.out.println(lazySingleton); System.out.println(eagerSingleton); } 

输出:

 LazySingleton was created 0 ms ago EagerSingleton was created 1001 ms ago 

如您所见,在调用Class.forName()时可以创建非包装的简单实现,这可能是准备执行静态初始化之前

只有在懒惰地初始化单例时,才需要在getInstance内部进行同步。 如果您可以在线程启动之前创建实例,则可以在getter中删除同步,因为引用变为不可变。 当然,如果单例对象本身是可变的,则需要同步其访问可以同时更改的信息的方法。

这个问题实际上取决于您的实例的创建方式和时间。 如果你的getInstance方法懒洋洋地初始化:

 if(instance == null){ instance = new Instance(); } return instance 

然后你必须同步,否则你最终可能会有多个实例。 这个问题通常在Double Checked Locking的谈话中处理。

否则,如果您预先创建静态实例

 private static Instance INSTANCE = new Instance(); 

然后不需要同步getInstance()方法。

没有人像Effective Java中那样使用Enums?

有效java中描述的最佳方法是:

 public class Singelton { private static final Singelton singleObject = new Singelton(); public Singelton getInstance(){ return singleObject; } } 

无需同步。

如果您确定您的Java运行时正在使用新的JMM(Java内存模型,可能比5.0更新) ,则双重检查锁定就好了,但在实例前面添加了一个volatile。 否则,你最好像Bohemian所说的那样使用静态内部类,或者像Florian Salihovic所说的那样使用“有效Java”中的Enum。

为简单起见,我认为使用枚举类是一种更好的方法。 我们不需要进行任何同步。 Java by construct,总是确保只创建了一个常量,无论有多少线程试图访问它。

仅供参考,在某些情况下,您需要将singleton替换为其他实现。 然后我们需要修改类,这违反了Open Close principal.Problem with singleton,你不能因为有私有构造函数而扩展类。 因此,客户端通过界面进行交谈是一种更好的做法。

使用枚举类和接口实现Singleton:

Client.java

 public class Client{ public static void main(String args[]){ SingletonIface instance = EnumSingleton.INSTANCE; instance.operationOnInstance("1"); } } 

SingletonIface.java

 public interface SingletonIface { public void operationOnInstance(String newState); } 

EnumSingleton.java

 public enum EnumSingleton implements SingletonIface{ INSTANCE; @Override public void operationOnInstance(String newState) { System.out.println("I am Enum based Singleton"); } }