我正在学习基本的软件设计模式。
单例类的基本实现是这样写的:
public class MyObject{
private volatile static MyObject obj;
private MyObject(){/*Do some heavy stuff here*/}
public static synchronized MyObject getInstance(){
if(obj==null)
obj=new MyObject();
return obj;
}
}
但是因为我不知道调用同步方法可能很重。
我回过头来看一本书,介绍了Singleton类的这种实现:public class MyObjectHolder {
private volatile static Supplier<MyObject> myObjectSupplier = () -> createMyObj();
//myObjectSupplier is changed on the first 'get()' call
public static MyObject getMyObject(){
return myObjectSupplier.get();
}
private static synchronized MyObject createMyObj(){
class MyObjectFactory implements Supplier<MyObject> {
private final MyObject clockTimer = new MyObject();
public MyObject get() { return clockTimer; }
}
if(!MyObjectFactory.class.isInstance(myObjectSupplier)) {
myObjectSupplier = new MyObjectFactory();
}
return myObjectSupplier.get();
}
public static class MyObject{
private MyObject(){
/*Do some heavy stuff here*/
}
public void someMethod(){
/* ... */
}
}
}
...
{
/*In main MyObject instantiation*/
MyObjectHolder.MyObject obj = MyObjectHolder.getMyObject();
}
现在,在第一次调用'createMyObj()之后,已经完成了同步方法调用的负担,如果没有检查则没有。
您认为此类实施存在问题吗?
PS。 MyObject不一定是MyObjectHold的内部类,但我觉得它看起来不错。
答案 0 :(得分:5)
[已更新] 另一种称为Initialization on Demand Holder idiom的解决方案:
public class SingletonObject {
private static final AtomicInteger INSTANCE_COUNT = new AtomicInteger();
private static final AtomicInteger INVOKE_COUNT = new AtomicInteger();
private static final class LazyHolder {
private static final SingletonObject INSTANCE = new SingletonObject();
}
private SingletonObject() {
System.out.println("new SingletonObject");
INSTANCE_COUNT.getAndIncrement();
}
public static SingletonObject getInstance() {
INVOKE_COUNT.getAndIncrement();
return LazyHolder.INSTANCE;
}
public static int getInstanceCount() {
return INSTANCE_COUNT.get();
}
public static int getInvokeCount() {
return INVOKE_COUNT.get();
}
}
证明它是线程安全的:
public static void main(String[] args) throws Exception {
int n = 1000;
List<Callable<SingletonObject>> invokers = new ArrayList<>();
for (int i = 0; i < n; i++) {
invokers.add(SingletonObject::getInstance);
}
ExecutorService es = Executors.newFixedThreadPool(n);
es.invokeAll(invokers);
es.shutdown();
System.out.println("Number of Instances = " + SingletonObject.getInstanceCount());
System.out.println("Number of Invokes = " + SingletonObject.getInvokeCount());
}
输出:
new SingletonObject
Number of Instances = 1
Number of Invokes = 1000
编辑(@Holger评论后):
使用嵌套持有人类 有点必要 懒惰地初始化 SingletonObject
。
public class SingletonObject {
private static final SingletonObject INSTANCE = new SingletonObject();
private SingletonObject() {
System.out.println("new SingletonObject");
}
public static SingletonObject getInstance() {
return INSTANCE;
}
public static void anotherStaticMethod() {
System.out.println("I don't need the SingletonObject Instance...");
}
}
那么如果有人调用anotherStaticMethod()
?
new SingletonObject
I don't need the SingletonObject Instance...
更新:
WIKIPEDIA处的页面说:
习惯用法的实现依赖于Java语言规范(JLS)指定的Java Virtual Machine(JVM)内的执行初始化阶段。当JVM加载类
SingletonObject
时,该类将进行初始化。由于该类没有任何静态变量来初始化,因此初始化完成很简单。在JVM确定必须执行LazyHolder
之前,不会初始化其中的静态类定义LazyHolder
。静态类LazyHolder
仅在类getInstance
上调用静态方法SingletonObject
时执行,并且第一次发生这种情况时,JVM将加载并初始化LazyHolder
类。LazyHolder
类的初始化导致通过执行外部类INSTANCE
的(私有)构造函数来初始化静态变量SingletonObject
。由于类初始化阶段由JLS保证是串行的,即非并发的,因此在加载和初始化期间静态getInstance
方法中不需要进一步的同步。并且由于初始化阶段在串行操作中写入静态变量INSTANCE
,因此getInstance
的所有后续并发调用将返回相同的正确初始化INSTANCE
,而不会产生任何额外的同步开销。 这提供了一个高效的线程安全“单例”缓存,没有同步开销;基准测试表明它比甚至无竞争同步快得多。然而,这个习语是单一的特定的,不能扩展到多个对象(例如基于地图的缓存)。
同时留意this。
答案 1 :(得分:3)
在java中实现Singleton模式的最简单方法是简单地将类设为enum:
public enum MyObject{
Obj;
MyObject(){/*Do some heavy stuff here*/}
}
规范保证Obj只在首次使用时创建一次。
答案 2 :(得分:2)
单身模式中断Single Responsibility Principle(SRP) - 因为该类必须做两件事:
您的第二种方法是尝试将此“强制单身人士”委托给一个单独的类 - 遵循SRP。如果您正在使用像spring这样的依赖注入框架,那么只需定义MyObject类并在spring上下文中使用'singleton'范围声明此类就可以实现相同的效果。