在多线程环境中使用 Singleton 类的首选方式是什么?
假设如果我有 3 个线程,并且它们都尝试同时访问单例类的 getInstance()
方法 -
- 如果不保持同步会怎样?
- 使用
synchronized
getInstance()
方法还是在getInstance()
中使用synchronized
block 是好习惯吗.
请告知是否有其他出路。
最佳答案
如果您说的是 threadsafe , lazy initialization的singleton ,这是一个很酷的代码模式,它可以在没有任何同步代码的情况下完成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
注释。
编辑:
一个特定的不信者声称包装类“什么都不做”。这证明它确实很重要,尽管是在特殊情况下。
与包装类版本的基本区别在于,在加载包装类时会创建单例实例,这在第一次调用 getInstance()
时会创建,但与非包装版本 - 即简单的静态初始化 - 加载主类时创建实例。
如果您只是简单地调用 getInstance()
方法,那么 几乎 没有区别 - 区别在于所有 other sttic 初始化会在之前在使用包装版本创建实例之前完成,但这很容易通过简单地在源代码中列出last的静态实例变量来解决。
但是,如果您按name 加载类,情况就完全不同了。在类上调用 Class.forName(className)
会导致发生静态初始化,因此如果要使用的单例类是服务器的属性,则使用简单版本时,将创建静态实例 Class.forName()
被调用,not 当 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;
}
}
还有主要的:
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);
}
输出:
LazySingleton was created 0 ms ago
EagerSingleton was created 1001 ms ago
如您所见,未包装的简单实现是在调用 Class.forName()
时创建的,这可能是在静态初始化准备就绪之前被执行。
关于java - Java中的单例和多线程,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11072262/