大厂面试必备之设计模式:漫画单例模式

大厂面试必备之设计模式:漫画单例模式_第1张图片
大厂面试必备之设计模式:漫画单例模式_第2张图片
大厂面试必备之设计模式:漫画单例模式_第3张图片
大厂面试必备之设计模式:漫画单例模式_第4张图片
大厂面试必备之设计模式:漫画单例模式_第5张图片
大厂面试必备之设计模式:漫画单例模式_第6张图片
【首先不管何种形式实现单例模式,构造方法一定是私有的,这是大前提。】

饿汉模式
饿汉模式中的类实例是当类被加载时就被初始化出来的,所以在应用初始化时,会占用不必要的内存。同时,由于该实例在类被加载的时候就创建出来了,所以他是线程安全的。因为类的初始化是由ClassLoader完成的,利用了ClassLoader的线程安全机制,ClassLoader的loadClass方法在加载类的时候使用了synchronized关键字实现线程同步。

public class Singleton {  
    private static Singleton instance = new Singleton();  
    private Singleton (){}  
    public static Singleton getInstance() {  
    return instance;  
    }  
}

instance对象在Singleton类被加载的时候,被实例化出来,他的实例化跟着类加载一起进行,很简单,保持了唯一性。
大厂面试必备之设计模式:漫画单例模式_第7张图片大厂面试必备之设计模式:漫画单例模式_第8张图片
通过静态内部类来实现的饿汉模式

public class Singleton {  
    private static class SingletonHolder {  
    private static final Singleton INSTANCE = new Singleton();  
    }  
    private Singleton (){}  
    public static final Singleton getInstance() {  
    return SingletonHolder.INSTANCE;  
    }  
} 

饿汉模式在类被加载时,就创建出对象,而通过静态内部类的方式,Singleton对象被加载时,INSTANCE没有被初始化,SingletonHolder类不会被加载,只有在调用getInstance()方法时,才会加载SingletonHolder类,实例化INSTANCE对象。由于类的初始化是由ClassLoader完成的,利用了ClassLoader的线程安全机制,所以通过静态内部类来实现的饿汉模式既不过早消耗资源,又能保证线程安全。
大厂面试必备之设计模式:漫画单例模式_第9张图片
大厂面试必备之设计模式:漫画单例模式_第10张图片
大厂面试必备之设计模式:漫画单例模式_第11张图片
大厂面试必备之设计模式:漫画单例模式_第12张图片
懒汉式
懒汉式顾名思义就是不会提前做准备,在使用的时候,才会实例化对象。

public class Singleton {  
    private static Singleton instance;  
    private Singleton (){}  

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

上面的代码很简单,通过 if (instance == null) 判断是否已经存在instance对象,存在的话,直接返回,不存在,则实例出instance对象。
大厂面试必备之设计模式:漫画单例模式_第13张图片
大厂面试必备之设计模式:漫画单例模式_第14张图片
大厂面试必备之设计模式:漫画单例模式_第15张图片
大厂面试必备之设计模式:漫画单例模式_第16张图片
大厂面试必备之设计模式:漫画单例模式_第17张图片
大厂面试必备之设计模式:漫画单例模式_第18张图片
1)粗暴式加锁

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

可见在getInstance()方法上增加synchronized,通过锁就可以实现线程安全,但是这种形式加锁的范围是整个初始化方法,效率很低,因为加锁的目的是保证第一次创建对象是同步的,不是第一次创建对象的情况,没有必要进行同步,可以直接返回instance。当多个线程调用getInstance()方法时,全部在等第一个线程释放锁,效率不高。

2)双重锁

public class Singleton {  
    private volatile static Singleton singleton;  
    private Singleton (){}  
    public static Singleton getSingleton() {  
    if (singleton == null) {  
        synchronized (Singleton.class) {  
        if (singleton == null) {  
            singleton = new Singleton();  
        }  
        }  
    }  
    return singleton;  
    }  
}  

相信双重锁对很多人来说并不陌生,尤其是经常面试的同学,单例双重锁是面试经常被问到的。
首先,通过使用同步代码块的方式减小了锁的范围,提高了效率,同时引入volatile阻止对象初始化的指令重排,实现多线程同步。
大厂面试必备之设计模式:漫画单例模式_第19张图片
大厂面试必备之设计模式:漫画单例模式_第20张图片

当执行singleton = new Singleton(); 语句时,正常会分下面三个步骤:
1)分配内存
2)初始化对象
3)将singleton指向分配的内存地址
可以看到,实例化对象并不是原子操作,并且编译器可能会指令重排,比如以上步骤被重排为下面的步骤:
1)分配内存
2)将singleton指向分配的内存地址
3)初始化对象
这样的话,如果线程A先分配内存,再singleton指向分配的内存地址 ,最后初始化对象时可能会出现如下情况:当线程A还没有执行3)初始化对象时,线程2执行到 if (singleton == null) 语句,因为线程A已经把singleton指向了内存地址,所以if (singleton == null) 语句返回false,getSingleton()方法则直接把未初始化的singleton对象返回回去,这个时候,线程B用到singleton对象时,就会出现空指针。我们通过volatile来阻止指令重排,从而避免上面问题的发生。

大厂面试必备之设计模式:漫画单例模式_第21张图片
大厂面试必备之设计模式:漫画单例模式_第22张图片


如有错误欢迎指出来,一起学习。
在这里插入图片描述

你可能感兴趣的:(漫画设计模式)