不同种类的单身人士模式

Nab*_*ska 4 java java-8

我正在学习基本的软件设计模式.

单例类的基本实现是这样写的:

    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;
       }    
    }
Run Code Online (Sandbox Code Playgroud)

但是因为我不知道调用同步方法可能很重.

我回来的时候我红了一本书介绍了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();

    }
Run Code Online (Sandbox Code Playgroud)

现在,在第一次调用'createMyObj()'之后,已经完成了同步方法调用没有沉重负担,如果没有检查则不是.

你觉得这种实现有什么问题吗?

PS.MyObject不一定是MyObjectHold的内部类,但我认为它看起来不错.

FaN*_*NaJ 5

[更新]另一种称为Initialization on Demand Holder的解决方案:

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

}
Run Code Online (Sandbox Code Playgroud)

证明它是线程安全的:

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());
}
Run Code Online (Sandbox Code Playgroud)

输出:

new SingletonObject
Number of Instances = 1
Number of Invokes = 1000
Run Code Online (Sandbox Code Playgroud)

编辑(在@Holger的评论之后):

使用的嵌套holder类有些必要懒洋洋初始化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...");
    }

}
Run Code Online (Sandbox Code Playgroud)

那么如果有人调用会发生什么anotherStaticMethod()呢?

new SingletonObject
I don't need the SingletonObject Instance...
Run Code Online (Sandbox Code Playgroud)

更新:

WIKIPEDIA的页面说:

习语的实现依赖于Java语言规范(JLS)指定的Java虚拟机(JVM)内的执行初始化阶段.当类SingletonObject由JVM加载时,该类将进行初始化.由于该类没有任何静态变量来初始化,因此初始化完成很简单.LazyHolder在JVM确定LazyHolder必须执行之前,不会初始化其中的静态类定义.静态类LazyHoldergetInstance在类上调用静态方法时执行SingletonObject,并且第一次发生这种情况时,JVM将加载并初始化LazyHolder类.LazyHolder类的初始化导致INSTANCE通过执行外部类的(私有)构造函数来初始化静态变量SingletonObject.由于JLS保证类初始化阶段是串行的,即非并发的,因此getInstance在加载和初始化期间静态方法不需要进一步的同步.并且由于初始化阶段将静态变量写入INSTANCE串行操作,因此所有后续的并发调用getInstance将返回相同的正确初始化,INSTANCE而不会产生任何额外的同步开销.这提供了一个高效的线程安全 "单例"缓存,没有同步开销; 基准测试表明它比甚至无竞争同步快得多.然而,该习语是单一特定的并且不可扩展到多个对象(例如,基于地图的高速缓存).

还要留意这一点.