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

前言

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

一、手写单例模式

单例模式的三个主要特点:

1、构造方法私有化;
2、实例化的变量引用私有化;
3、获取实例的方法共有
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;
    }
}
(3) 使用静态内置类实现单例模式
public class Singleton {
    private static class MyObjectHandler{
        private static Singleton instance=new Singleton();
    }
    private Singleton(){

    }

    public static Singleton getInstance(){
        return MyObjectHandler.instance;
    }
}

上面的代码和DCL一样,都是在多线程中经常用到的,她有两个特性,一个是实现了延迟加载,那么如何实现了延迟加载的呢?

静态内部类的创建不需要依赖于外围类,它不能使用任何外围类的非static成员变量和方法,某种程度上来说,静态内部类可以当成是一个独立的类。它的初始化和外部类初始化无关。所以只有真正调用getInstance() 方法,执行MyObjectHandler.instance时,才会引发静态内部类初始化,所以,静态内部类实现了延迟加载。

如何保证线程安全的?
首先静态内部类有static修饰符,static表示随着类的加载而加载,只加载一次,所以不会出现多个不同的对象,这样就达到了线程安全,这块是我自己理解的,如有不对欢迎指正。

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

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

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值