寂然解读设计模式 - 单例模式(上)

I walk very slowly, but I never walk backwards 

设计模式 - 单例模式(上)


寂然

大家好~,我是寂然,本节课呢,我们正式进入设计模式的学习,第一个要聊的就是我们最常见的,同时也是面试高频的单例模式,那老规矩,我们直接启程吧

五重境界

在此之前,我们闲聊两句,因为有的小伙伴问到一个问题,怎么才算是学会或者说掌握设计模式了呢?那我们一起来聊聊这个话题,会有很多人说其实这个不好去定义,但是如果自己平时写代码,无意识,习惯成自然的用到了设计模式,遇到实际问题,很快就能对比选择,使用合适的设计模式,低耦合,合理的解决掉实际问题,我觉得就是很好的掌握了,网上有人分享了设计模式的五重境界,感觉很贴切,这里也拿来和大家分享下


第 1 层:刚开始学编程不久,听说过什么是设计模式 ,但不了解

第 2 层:有很长时间的编程经验了,自己写了很多代码,其中用到了设计模式,但是自己却不知道

第 3 层:学习过了设计模式,发现自己已经在使用了,并且发现了一些新的模式挺好用的

第 4 层:阅读了很多别人写的源码和框架,其中看到了设计模式,能够领会设计模式的精妙和带来的好处

第 5 层:代码写着写着,自己都没有意识到使用了设计模式,并且熟练的写了出来

问题答疑

同时,在开始之间还要和大家强调一句话,因为很多小伙伴都有说或者脑海中有这样一个疑问:这样就可以啊,为什么非要这么复杂呢,大家要注意,设计模式不仅仅是站在实现功能的角度上,而是立足于整个软件系统的设计,所以大家要学会在学习的过程中,总结和对比思考,可以达到事半功倍的效果

官方定义

所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例并且该类只提供一个取得其对象实例的方法(静态方法)


举个最常见的例子,Spring 中的 bean 默认都是单例模式,每个bean定义只生成一个对象实例,每次getBean请求获得的都是此实例

单例模式八种方式

那接下来我们来聊聊单例模式的八种实现方式,如下所示

饿汉式(静态常量)

饿汉式(静态代码块)

懒汉式(线程不安全)

懒汉式(线程安全,同步方法)

懒汉式(线程安全,同步代码块)

双重检查

静态内部类

枚举方式

饿汉式(静态常量)

饿汉式之静态常量的写法如下:

一,构造器私有化(防止外部通过new + 构造器的方式创建对象实例)

二,类的内部创建对象(创建final,static的实例对象)

三,对外暴露一个公共的静态方法(通过该方法,返回该类唯一的对象实例)

// 饿汉式(静态变量)
class Singleton{
    //一 构造器私有化
    private Singleton(){

    }
    //类的内部创建对象
    private final static Singleton singleton = new Singleton();

    //对外提供公共的,静态的方法
    public static Singleton getInstance(){

            return singleton;
    }
}

public class SingletonDemo {
    public static void main(String[] args) {

        //测试
        Singleton instance = Singleton.getInstance();
        Singleton instance1 = Singleton.getInstance();
        System.out.println(instance == instance1);
        //结果为 true,证明是同一个实例对象
    }
}
写法分析

优势

这种写法比较简单,就是在类装载的时候就完成了实例化,避免了线程同步问题

这种方式基于 classloder 机制避免了多线程的同步问题,不过,instance 在类装载时就实例化,在单例模式中大 多数都是调用 getInstance 方法,但是导致类装载的原因有很多种,因此不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化 instance 就没有达到 lazy loading 的效果

劣势

在类装载的时候就完成实例化,没有达到 Lazy Loading 的效果,如果从始至终从未使用过这个实例,则会造成内存的浪费

饿汉式(静态代码块)

饿汉式(静态代码块)的方式,是在静态代码块中创建单例对象,示例代码如下图所示

//饿汉式 静态代码块
class Singleton{

    private Singleton(){

    }

    private final static Singleton singleton;

    static {
        singleton = new Singleton();
    }

    public static Singleton getInstance(){

        return singleton;
    }
}

public class Singletondemo02 {

    public static void main(String[] args) {

        Singleton instance = Singleton.getInstance();
        Singleton instance1 = Singleton.getInstance();
        System.out.println(instance == instance1);

    }
}
写法分析

这种方式和上面的其实类似,只不过将类实例化的过程放在了静态代码块中,也是在类装载的时候,就执行静态代码块中的代码,初始化类的实例,优劣势和第一种写法一致,写法可用,但可能会造成内存的浪费

饿汉式是不存在线程安全问题的

懒汉式(线程不安全)

懒汉式写法与饿汉式写法的不同在于,并不是一上来就要创建对象,而是当调用对应的getInstance()方法时,才会进行创建,示例代码如下图所示

// 懒汉式(线程不安全)
class Singleton{
    
    //构造器私有化
    private Singleton(){ 
    
    }

    //类的内部创建对象
    private static Singleton singleton;

    //提供一个静态的公有方法,当使用到该方法时,才会去创建 instance
    public static Singleton getInstance(){

        fiif (singleton == null){
            singleton = new Singleton();
        }
        return singleton;
    }
}

public class SingletonDemo {
    public static void main(String[] args) {
        Singleton instance = Singleton.getInstance();
        Singleton instance1 = Singleton.getInstance();
        System.out.println(instance == instance1);

    }
}
写法分析

优势

起到了 lady loading 的效果,不会造成内存浪费,实例使用到的时候,调用getInstance()方法才会创建

劣势

只能在单线程下使用,多线程会出现线程不安全的问题

试想,如果在多线程下,一个线程进入了 if (singleton == null){ ...} 判断语句块,还没来得及往下执行,另一个线程进行判断,此时实例还未来得及创建,也通过了这个判断语句,这时便会产生多个实例,出现了线程不安全问题

所以,在实际开发中,不建议使用这种方式

懒汉式(同步方法)

针对上面线程不安全的问题,我们在懒汉式的代码中,加入同步处理,第一种就是同步方法,相关代码如下

//懒汉式 同步方法
class Singleton{

    //构造器私有化
    private Singleton(){

    }

    //类的内部创建对象
    public static Singleton singleton;

    //对外提供公共的静态方法,加入同步处理
    public static synchronized Singleton getInstance(){

        if (singleton == null){
            singleton = new Singleton();  
        }
        return singleton;
    }
}

public class SingletonDemo02 {
    public static void main(String[] args) {

        Singleton instance = Singleton.getInstance();
        Singleton instance1 = Singleton.getInstance();
        System.out.println(instance == instance1);
    }
}
写法分析

首先,这种写法,由于加入了同步处理,解决了线程不安全的问题

但是我们说,这样效率很低,每个线程在想获得类的实例的时候,执行 getInstance() 方法都要进行同步,而其实这个方法只执行一次实例化代码就够了,后面的想获得该类的实例,直接return就好了,方法进行同步,效率很低

所以,在实际开发中,也不推荐使用这种方式

懒汉式(同步代码块)

那既然方法进行同步,效率很低,我们可不可以使用同步代码块的方式呢?大家看如下的示例代码

class Singleton{

    //构造器私有化
    private Singleton(){ }

    //类的内部创建对象
    public static Singleton singleton;

    //对外提供公共的静态方法
    public static Singleton getInstance(){

        if (singleton == null){

            synchronized (Singleton.class){
                singleton = new Singleton();  
            }

        }
        return singleton;
    }
}
写法分析

这种写法就是,本意是想对第四种实现方式的改进,在创建实例对象的时候,进行同步,那大家思考一下,这样可行嘛?可以保证线程安全嘛


但其实,如果在多线程下,一个线程进入了 if (singleton == null){ ...} 判断语句块,还没来得及往下执行,另一个线程进行判断,此时实例还未来得及创建,也通过了这个判断语句,便会产生多个实例,所以在里面创建实例对象的时候,进行同步没有实际意义,在实际开发中,不能使用这种方式

下节预告

OK,由于篇幅的限制,本节内容就先到这里,下一节,我们接着来聊单例模式的后三种写法,包括双重检查机制,静态内部类,枚举,同时会带大家看下JDK源码中单例模式的应用,以及对单例模式的注意事项进行总结,最后,希望大家在学习的过程中,能够感觉到设计模式的有趣之处,高效而愉快的学习,那我们下期见~

你可能感兴趣的:(寂然解读设计模式 - 单例模式(上))