viv*_*011 22 java singleton multithreading design-patterns
在多线程环境中使用Singleton类的首选方法是什么?
假设我有3个线程,并且所有线程都试图同时访问getInstance()singleton类的方法 -
synchronized getInstance()方法或使用synchronized块是好的做法getInstance().请告知是否还有其他方法.
Boh*_*ian 35
如果你谈论的是线程安全的,延迟初始化的的单,这里是使用完成一个很酷的代码模式100%线程延迟初始化没有任何同步代码:
public class MySingleton {
private static class MyWrapper {
static MySingleton INSTANCE = new MySingleton();
}
private MySingleton () {}
public static MySingleton getInstance() {
return MyWrapper.INSTANCE;
}
}
Run Code Online (Sandbox Code Playgroud)
这将仅在getInstance()调用时实例化单例,并且它是100%线程安全的!这是经典之作.
它的工作原理,因为类加载器都有自己的处理类的静态初始化同步:您保证所有静态初始化完成使用前级,并在此代码的类是内只使用getInstance()方法,所以这是上课的时候loading加载内部类.
@Singleton顺便说一句,我期待有一个处理此类问题的注释存在的那一天.
一个特别不相信的人声称包装类"什么都不做".这是证明它确实很重要,尽管在特殊情况下.
基本的区别在于,使用包装器类版本时,单例实例是在加载包装器类时创建的,当第一次调用时,创建单例实例getInstance(),但是使用非包装版本 - 即简单的静态初始化 - 创建实例当主类加载时.
如果你只是简单地调用该getInstance()方法,那么几乎没有区别 - 不同之处在于,在使用包装版本创建实例之前,所有其他 sttic初始化都已完成,但这很容易通过简单地处理源中最后列出的静态实例变量.
但是,如果您按名称加载类,则故事情况则完全不同.调用要Class.forName(className)进行静态初始化的类,因此如果要使用的单例类是服务器的属性,则使用简单版本时,Class.forName()将调用静态实例,而不是在getInstance()调用时创建静态实例.我承认这有点做作,因为你需要使用反射来获取实例,但是这里有一些完整的工作代码来演示我的争用(以下每个类都是顶级类):
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;
}
}
Run Code Online (Sandbox Code Playgroud)
主要:
public static void main(String[] args) throws Exception {
// Load the class - assume the name comes from a system property etc
Class<? extends BaseSingleton> lazyClazz = (Class<? extends BaseSingleton>) Class.forName("com.mypackage.LazySingleton");
Class<? extends BaseSingleton> eagerClazz = (Class<? extends BaseSingleton>) 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);
}
Run Code Online (Sandbox Code Playgroud)
输出:
LazySingleton was created 0 ms ago
EagerSingleton was created 1001 ms ago
Run Code Online (Sandbox Code Playgroud)
如您所见,Class.forName()调用时会创建非包装的简单实现,这可能是在准备执行静态初始化之前.
Pet*_*der 16
理论上,这项任务非常重要,因为您希望使其真正具有线程安全性.
@ 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;
}
Run Code Online (Sandbox Code Playgroud)
所以,你应该采取一些不那么懒惰的东西:
class Singleton {
private static Singleton instance = new Singleton();
private Singleton() { }
public static Singleton getInstance() {
return instance;
}
}
Run Code Online (Sandbox Code Playgroud)
或者,更加臃肿,但更灵活的方法是避免使用静态单例并使用Spring等注入框架来管理"singleton-ish"对象的实例化(并且可以配置延迟初始化).
getInstance只有在懒惰地初始化单例时才需要内部同步.如果您可以在线程启动之前创建实例,则可以在getter中删除同步,因为引用变为不可变.当然,如果单例对象本身是可变的,则需要同步其访问可以同时更改的信息的方法.