【java 设计模式】 单例模式精解(面试再也不用怕了)

前言

这篇 博客是跑更问底的学习单例模式,看了本博客,对于一般的面试官,你都可以手撕了,但是大神级别的面试官,后边还会补充。

一、手写单例模式

1、饿汉式
public class Singleton {
    // 构造方法私有化,其他类就不能通过new的方式来创造对象
    private Singleton(){
    }
    // 内部提供一个当前的实例,必须要静态化,因为下面的静态方法要调用
    private static Singleton singleton=new Singleton();
    // 提供公共的静态方法,返回当前类的对象,外部类调用的唯一路径
    public static Singleton getInstance(){
        return singleton;
    }
}
2、懒汉式
public class Singleton {
    private Singleton() {
    }
    private static Singleton singleton = null;
    public static Singleton getInstance() {
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
}
3、对比分析

首先饿汉式,就是迫不及待的new 出一个对象,然后不管你调不调用,我都要new 出一个对象,这样虽说是线程安全的,但是对象加载的时间长,耗费内存。 懒汉式,因为它是懒加载,什么时候用,什么时候new 对象,延时了对象的创建,节省内存空间,但是它是线程不安全的。如果不知道为啥是线程不安全的,还请看我之前写的多线程的系列博客。

4、jdk中单例模式应用举例

jdk中的RunTime 就是饿汉式的 ,如下:

public class Runtime {
    private static Runtime currentRuntime = new Runtime();

    /**
     * Returns the runtime object associated with the current Java application.
     * Most of the methods of class <code>Runtime</code> are instance
     * methods and must be invoked with respect to the current runtime object.
     *
     * @return  the <code>Runtime</code> object associated with the current
     *          Java application.
     */
    public static Runtime getRuntime() {
        return currentRuntime;
    }

    /** Don't let anyone else instantiate this class */
    private Runtime() {}

二、饿汉式线程不安全的解决方式

(1)synchronized关键字实现线程同步
public class Singleton {
    private Singleton() {
        System.out.println("hahahaha");
    }
    private static Singleton singleton = null;
    public  static  Singleton getInstance() {
        synchronized (Singleton.class){
            if (singleton == null) {
                singleton = new Singleton();
            }
            return singleton;
        }
    }
}

上面的代码效率稍差,先不提synchronized()重量级锁的事情,是因为当singleton不是null的时候,其他线程每次进来的时候都要去判断有没有Singleton.class这个锁,效率差在了这里,我们稍微改进一下,用双端检索机制(DCL (double check lock))进行修改.

(2) DCL 方式提高效率
public class Singleton {
    private Singleton() {
        System.out.println("hahahaha");
    }

    private static Singleton singleton = null;

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

到这里你就觉得完美了,重点来了,上面的双端监测的方式也不一定是线程安全的,因为还有指令重排的存在。这会导致某一个线程执行到第一次检测,读取到的singleton不为null时,singleton的引用对象可能没有完成初始化

因为singleton=new Singleton();可以分为三步
伪代码

memory=allocate(); //1、分配对象内存空间
singleton(memory);//2、初始化对象
singleton=memory;//3、设置singleton指向刚分配的内存地址,此时instance!=null

步骤2和步骤3不存在数据依赖关系.而且无论重排前还是重排后程序执行的结果在单线程中并没有改变,因此这种重排优化是允许的.

memory=allocate();//1.分配对象内存空间
singleton=memory;//3.设置singleton指向刚分配的内存地址,此时instance!=null 但对象还没有初始化完.
singleton(memory);//2.初始化对象

由上可知,当一条线程访问singleton不为null时,由于存在singleton实例未完成初始化的可能性,此时就造成了线程安全问题。

既然是指令重排导致的问题,我们前边介绍了volatile关键字的作用了, 看一看这篇博客 https://blog.csdn.net/jerry11112/article/details/106870835,其中volatile可以保证原子性,禁止指令重排,所以我们加上volatile关键字就好

public class Singleton {
    private Singleton() {
    }

    private static volatile Singleton singleton = null;

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

三、单例模式——应用场景

1、应用程序的日志应用,一般都使用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只有一个实例去操作,否则内容不好追加。
2、数据库连接池
3、网站计数器
4、Spring中的单例模式,Spirng bean有一个属性为scope 其中默认就是singleton 就是单例模式

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章