JavaEE设计模式——单例模式(饿汉模式、懒汉模式)安全问题

单例模式,是一种经典的设计模式,在校招中最常考的设计模式之一。设计模式就像是软件开发中的棋谱,是大佬们针对一些常见场景,总结出来的代码的编写套路~~


目录

一、设计模式的概念

二、单例模式的概念

2.1懒汉模式(从容)

2.2饿汉模式(急迫)

2.3计算机中的例子

三、饿汉模式代码实现

四、懒汉模式代码实现

五、懒汉模式和饿汉模式的线程安全问题

5.1解决方法一

5.2解决方法二

5.3解决方法三

六、懒汉模式指令重排序问题

七、总结单例模式


一、设计模式的概念

什么是设计模式:

设计模式好比象棋中的 "棋谱".,红方当头炮, 黑方马来跳, 针对红方的一些走法, 黑方应招的时候有一些固定的套路,按照套路来走局势就不会吃亏

就是大佬们总结的一些固定的套路,按照这个套路来实现代码,也不会吃亏

二、单例模式的概念

单例 => 单个实例(instance)对象。类的实例,就是对象,单例就是一个程序中,某个类,只创建出一个实例(一个对象),不能创建多个对象。

Java中的单例模式,借助Java语法,保证某个类,只能够创建出一个实例,而不能new多次,一般单例模式分为懒汉模式和饿汉模式~~

2.1懒汉模式(从容)

那么懒汉模式是什么?比如说,吃完饭之后需要洗碗,如果是懒汉模式,就会把碗放到一边,等到下一顿吃的时候,需要用到碗了再洗~~用几个碗洗几个碗~~

2.2饿汉模式(急迫)

那么饿汉模式又是什么?比如说,吃完饭之后需要洗碗,饿汉模式就会立即去洗碗~~全都洗了~

2.3计算机中的例子

打开一个硬盘上的文件,读取文件内存,并显示出来;

饿汉:把文件所有内容都读取到内存中,并显示

懒汉:只把文件读一小部分,把当前屏幕填充上,如果用户翻页了,再读其他文件内容,如果不翻页,就省下了~

假设文件非常大,饿汉可能要打开半天,懒汉就可以快速打开~

三、饿汉模式代码实现

//把这个类设定成单例的~~
class Singleton{
    //唯一实例的本体
    private static Singleton instance = new Singleton();

    //获取实例的方法
    public static Singleton getInstance(){
        return instance;
    }

    //禁止外部 new 实例
    private Singleton(){

    }
}

 唯一实例对象,被static修饰,该属性是类的属性,JVM中,每个类的类对象只有一份~~

 此处需要把new操作禁止掉!!设为private即可!!

四、懒汉模式代码实现

class SingletonLazy{
    private static SingletonLazy instance = null;

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

    private SingletonLazy(){
    }
}
public class ThreadDemo3 {
    public static void main(String[] args) {
        SingletonLazy s1 = SingletonLazy.getInstance();
        SingletonLazy s2 = SingletonLazy.getInstance();
    }
}

只需要在调用方法的时候才创建对象~~

五、懒汉模式和饿汉模式的线程安全问题

 只是单纯的读操作,不是修改操作,不会触发线程安全问题!!!所以是安全的

 如果多个线程下调用getInstance,懒汉模式就会出现问题,可能无法保证创建对象的唯一性!

 

 

假如N个线程,同时调用getInstance方法,可能就搞出N个对象了!!!

那么如何解决这种安全问题?

5.1解决方法一

加锁~~~

保证判定和new是一个原子操作 

加锁其实是一个比较低效的操作(加锁就可能涉及到阻塞等待),非必要,不加锁~~~

5.2解决方法二

也可以保证判定和new是一个原子操作,但是任何时候调用getInstance都会触发锁的竞争~~

其实此处的线程不安全,只出现在首次创建对象这里,一旦对象new好了,后续调用getInstance,就只是单纯的读操作,就没有线程安全问题,就没必要再加锁了!!

5.3解决方法三

双重if判断,这俩if条件目的是不同的,这俩个条件看起来代码一样,实际上他俩的执行时机,差别很大,因为if中间隔了个synchronized,加锁可能导致阻塞,什么时候解除阻塞,沧海桑田~~

第一个if为了判断是否需要加锁

第二个if为了创建对象

虽然俩个条件相同,但是如果调用的时间间隔长了,结果也可能会不同

六、懒汉模式指令重排序问题

代码中可能还会触发指令重排序问题~~

俩个线程同时调用getInstance方法时:

 这个操作可能会触发指令重排序~~

1.创建内存(买房子)

2.调用构造方法(装修)

3.把内存地址,付给引用(拿到钥匙)

当指令这样执行时:

 此时系统调度给t2了,再去判断条件,发现条件不成立,非空,直接返回实例的引用,接下来t2继续调用后续方法,可能就都是将错就错了!!!

因为t2还是个没装修的毛坯房

当然为了解决这个问题,加上volatile关键字就会避免

当然这个过程也是个小概率事件~~

七、总结单例模式

单例模式,线程安全问题:

饿汉模式:天然就是安全的,只有读操作

懒汉模式:不安全的,有读也有写

1.加锁:把if和new变成原子操作

2.双重if:减少不必要的加锁操作

3.使用volatile禁止指令重排序,保证后续线程拿到的是完整对象

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值