单例程的写法总结

什么是单例程?

其实啊,单例程是一种设计模式
那啥是设计模式呢?
设计模式就好比象棋中的棋谱。假如,红方当头炮,那么黑方就马来跳,这样就能有效应对红方的走棋。因此黑方应招的时候有一些固定的套路,按照套路走,局势就不会吃亏。
在软件开发中也有很多常见的问题场景,针对这些问题场景,大佬们总结出了一些固定的套路,按照这个套路来实现代码,就不会吃亏。由此可见啊,那些大佬为了我们可谓是操碎了心。

单例程的作用:

它能保证某个类在程序中只存在唯一一份实例,而不会创建出多个实例。

常见的实现方式:“饿汉模式”和“懒汉模式”

饿汉模式:

饿汉模式的本质就是:在类加载的同时创建实例。

//饿汉模式的实现:
class Singleton{
    //static成员的初始化是在类的加载的时候,此处可以简单的认为,就是jvm一启动,就立即加载,可见这个对象创建的时机很早。
    private static Singleton instance = new Singleton();
    //此处将构造方法设置为private修饰,是为了防止当在外部有人想再new一个对象的时候,会产生报错
    //产生报错的原因很简单,就是new的时候会调用构造方法,而构造方法却是private修饰的,不允许访问,所以会发生报错
    private Singleton(){}
    //可以通过getInstance这个方法,来获取已经new好的对象
    public static Singleton getInstance(){
        return instance;
    }

}

洁净板


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

上面代码中的解释想必已经很详细了,但是,我们再想一个问题:假如我把这个Singleton类改了,就在这个类里面new多个实例,要知道我们目的就是:为了防止创建多个实例。这样一来不是有点矛盾了吗
在这里插入图片描述

懒汉模式:

其实啊,“懒”这个字在计算机种并不是一个贬义词,而是一个褒义词,为什么呢?想一想如果你此时正在看电视,你口渴了想去喝水,但是你又懒的去的,于是你就没动,但是,过了一会你又饿了,你不得不去弄点吃的,然后顺便就把水喝了。这样你需要跑两趟的动作,一趟就完成了,是不是提高了效率捏。
本质:什么时候用到了再创建,用不到就不创建

//懒汉模式的实现:(初级)
class Singleton{
    public static Singleton instance = null;
    public static Singleton getInstance(){
        if(instance == null){
            instance = new Singleton();
        }
        return instance;
    }
    private Singleton(){}
}

在这里插入图片描述

两种模式的安全性问题(多线程)
  • “饿汉模式”是线程安全的。
  • “懒汉模式”是线程不安全的。
    原因:饿汉模式之所以安全是因为:
    在这里插入图片描述
    懒汉模式之所以不安全是因为:
  • 线程安全问题发生在首次创建实例时,如果在多个线程中同时调用getInstance方法,就可能导致创建出多个实例。

在这里插入图片描述
出现了线程不安全,我们的第一反应就是加锁。

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

在这里插入图片描述

  • 进一步改进:
    在这里插入图片描述
class Singleton{
    public static Singleton instance = null;
    private static Object locker = new Object();
    public static Singleton getInstance(){
        //将if和new打包成一个原子操作
            synchronized(locker){
                if(instance == null){
                instance = new Singleton();
            }
        }
        return instance;
    }
    private Singleton(){}
}

在这里插入图片描述
第二步改进:
在这里插入图片描述
注意

  • 上面的第一个 if 的作用是判断是否需要加锁,防止实例化以后,已经不需要加锁了,但是还是重复加锁的繁琐操作,防止低效。
  • 第二个 if 的作用是判断当前是否创建了实例,两个条件虽然相同但是含义不同。
    举个例子:
    在这里插入图片描述
    第三步优化:
    我们还要考虑内存可见性和指令重排序的问题:加上volatile就行了
//经过三步优化,这才是最安全的代码:
class Singleton{
    //加上volatile能避免内存可见性(一成)   和   指令重排序的问题(九成)
    public  static volatile Singleton instance = null;
    private static Object locker = new Object();
    public static Singleton getInstance(){
        //双if + 锁 更安全
        if(instance == null){
            synchronized(locker){
                if(instance == null){
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
    private Singleton(){}

指令重排序:

接下来我们就着重说一说指令重排序的问题,先举个例子:假如下面是一个菜市场的地图,我们需要去买几样菜给出几个买菜的先后顺序:
1)西红柿——》鸡蛋——》茄子——》黄瓜
2)黄瓜——》西红柿——》茄子——》鸡蛋
你觉得哪一个路线效率更高呢?
很明显肯定是 2
指令重排序就是把要执行的代码顺序调整后,最终的效果没变,但是效率提高了不少,重排序的前提一定是重新排序之后,逻辑和之前等价

  • 单线程下,编译器进行指令重排序的操作,一般都是没问题的,编译器可以精确的识别出,哪些操作可以重排序,而不会影响到逻辑。
  • 多线程下,判定可就不准确了,就有可能会出现重新排序后,逻辑发生了改变了——》引起bug

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

面试小技巧:
在这里插入图片描述

单例程的应用场景

在这里插入图片描述

  • 24
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值