不同种类的单身人士模式

时间:2016-01-31 16:25:54

标签: 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;
       }    
    }

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

我回过头来看一本书,介绍了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的内部类,但我觉得它看起来不错。

3 个答案:

答案 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) - 因为该类必须做两件事:

  1. 这是首要任务
  2. 执行单身人士。
  3. 您的第二种方法是尝试将此“强制单身人士”委托给一个单独的类 - 遵循SRP。如果您正在使用像spring这样的依赖注入框架,那么只需定义MyObject类并在spring上下文中使用'singleton'范围声明此类就可以实现相同的效果。