教你一步步写完美的单例模式

之前只会写固定的单例模式,没有仔细研究过。最佳在书上看到介绍一步步单例模式。不过是用cpp写的,与是自己用java一步步实现一遍。

Step1 适应于单线程的Singleton

public class Singleton {
    private Singleton() {}

    private static Singleton INSTANCE=null;

    public static Singleton getInstance(){
        if (INSTANCE==null) {
            INSTANCE=new Singleton();
        }
        return INSTANCE;     }
}

我们将构造方法设为private避免了类在外部被实例化,这样在同一个虚拟机范围内,Singleton的唯一实例只能通过getInstance()方法访问。并且我们的方法和成员变量都是静态的。

不足

这种方法如果在单线程中使用是能够正常运行的,但是如果我们是在多线程情况下呢?想一想,如果有两个或以上的同学运行到判断instance是否为null的if语句的时候,这个时候如果instance没有创建,那么这些线程都会创建instance。这是就不满足我们的单例要求了。

Step2 在多线程下的Singleton

public class Singleton {
    private Singleton() {}

    private static Singleton INSTANCE=null;

    public static synchronized Singleton getInstance(){
        if (INSTANCE==null) {
            INSTANCE=new Singleton();
        }
        return INSTANCE;     }
}

不细心的你可能会说这两行代码不是一样的吗。不对,我们对getInstance方法实现了同步锁。此时如果有多个线程想创建一个实例,因为在同一时刻只能由一个线程得到同步锁,当第一个线程加上锁时,后面的线程就需要等待。当第一个线程发现instance还没有创建的时候就会去创建。接着第一个线程释放同步锁,后面的线程加上同步锁,这时候实例已经由第一个线程创建了,所有第二个线程就不会重复的去创建了。

不足

虽然我们实现了多线程环境下的单例,但是你会发现我们每次在通过getInstance获取实例时,我们都视图去加上一个锁,但是加锁是一个耗时操作,我们应该尽量避免它。

Step3 避免加锁带来的耗时

我们只需要当我们的实例还没有创建的时候进行加锁防止多个线程同时创建实例,当实例已经创建的时候我们就应该避免加锁。

public class Singleton {
    private Singleton() {}

    private static Singleton INSTANCE=null;

    public static  Singleton getInstance(){
        if (INSTANCE==null) {
            synchronized (Singleton.class){
                if (INSTANCE==null) {
                    INSTANCE=new Singleton();    
                }
            }
        }
        return INSTANCE;     }
}

我们在加锁前进行一次判断,这样只有当instance不存在的时候才需要加锁操作。这样我们的单例写的比较完美了,但是if语句的判断容易增加我们代码的错误率,精益求精的我们肯定不止步于此,再来想想更加优秀的解法。

推荐解法一

public class Singleton {
    private Singleton() {}

    public static final Singleton getInstance(){
        return MyInstance.INSTANCE;
    }

    private static class MyInstance{
        private static final Singleton INSTANCE=new Singleton();
    }
}

对了,上没的几种方法和解法一都是我们常说的懒汉模式。懒汉模式实现的是按需加载的单例模式。只有当我们需要的时候调用getInstance方法才会实例化这个单例。

推荐解法二

public class Singleton{
    private Singleton() {}
    private static final Singleton INSTANCE=new Singleton();

    public static final Singleton getInstance(){
        return INSTANCE;
    }
}

上面这种就是饿汉模式了,饿汉就是类一旦加载,就把单例初始化完成,保证getInstance的时候,需要的实例是已经存在的了。因为饿汉模式在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以天生是线程安全的。

    原文作者:算法小白
    原文地址: https://juejin.im/entry/597b010e5188253dfc1fa27e
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞