单例模式线程安全问题

11 篇文章 0 订阅

java-code: java代码管理 (gitee.com)

文章目录

目录

一、单例模式概念

二、单例模式实现

1.饿汉模式

2.懒汉模式(非必要不创建)(优化)

如何解决懒汉模式的线程安全问题?

总结


一、单例模式概念

顾名思议就是类在线程中就只要一个实例~(在于发生禁止new,只能创建一个实例)

单例模式是常考的设计模式,还有一种叫工厂模式

二、单例模式实现

单例模式一般有两种实现方式!

懒汉模式和饿汉模式~

简单举个栗子:

两个汉字,一个好几天没吃饭了,一个吃的饱饱的,这时候如果有四碗大米饭,饿汉模式就会一口气全部吃完(不管自己饿不饿,不管有多少饭),如果是懒汉模式,他就会懒得吃饭,只有当不得不吃的时候才会吃,而且需要多少就吃多少(非必要,不吃饭~)

1.饿汉模式

一次读取所有的资源

代码实现:

//这个类设定成单例的

class Singleton{

    private static Singleton instance=new Singleton();   //创建实例  唯一的~

    public static Singleton getInstance(){     //获取实例的方法
        return instance;
    }
    private  Singleton(){};  //该类构造方法设置为 private禁止外部new实例~
        //这样就可以保证单例~

}
class Test {
    public static void main(String[] args) {
        //唯一实例都是同一个~
            Singleton s1=Singleton.getInstance();
            Singleton s2=Singleton.getInstance();
//            Singleton s3=new Singleton();   //这样就禁止了再次new对象了~
        System.out.println(s1 == s2);
    }
}

通过公共方法来从外部获取实例,通过private来限制外部再次创建实例~

2.懒汉模式(非必要不创建)(优化)

只有用的时候才会读取(通常来说懒汉模式来说他的效率更高)

class Singletons{

    private static Singletons singletons=null;

    public static Singletons getSingletons() {
        if (singletons == null) { //只有真正用的时候创建
            singletons = new Singletons();
        }
        return singletons;
    }
    private Singletons(){};
}
public class ThreadDome2 {
    public static void main(String[] args) {
        Singletons s1=Singletons.getSingletons();
        Singletons s2=Singletons.getSingletons();
        System.out.println(s1 == s2);
    }
}

但是既然是多线程,那么就得考虑多线程安全问题~

那么问题来了,这些代码是线程安全的吗?

思考一下,在饿汉模式中,没有实际的修改操作,只有读操作,所以饿汉模式是线程安全的,

反观懒汉模式,用到了比较,和修改,懒汉模式就会触发线程安全问题~

我们带入多线程的思维,此时如果是两个线程同时执行,t1判断singleton为空进入,t2同时也判断正确,进入,此时t1新new了一个对象,t2判断也正确,也new了一个对象,这样就无法保证是单例模式了~,那么如何解决这个问题呢? 很简单,我们只需要保证if和赋值操作是原子的就可以了~那么就给其加上锁~

但是加锁是个非常拖慢效率的方法,我们可以在保证结果正确的情况下,尽量少加锁,来提高程序的效率~

稍微改进一下  (我们分析一下,只有在第一次,没有对象的时候才需要new一个对象,需要用到if和new,如果已经有了对象,就可以不用加锁,直接返回你的对象就可以了~)

这两个if虽然一模一样,但是他们的初心和执行时间不一样,他俩之间隔了个加锁(阻塞),可能实际执行时间会间隔很久(沧海桑田)

这样就可以完全解决多线程的问题,但是仔细思考,其实还有个多线程安全问题,我们的new操作,可以分为3步CPU指令,这样就会发生指令重排序~

举例:此时t1线程执行到new了~第一步分配了内存,然后是先3再2,这样就导致还没初始化,就直接赋引用了,此时t2来调用,t2判断singleton就已经不为空了,此时t2线程拿到的就是没有初始化的线程,就会出现问题~

解决方法也非常简单,直接使用volatile来禁止指令重排序~

class Singletons{
    volatile private static Singletons singletons=null;
    public static Singletons getSingletons(){
        //如果对象已经创建过了,就不需要再创建了,直接返回就可以~
        if(singletons==null){
            synchronized (Singleton.class){    //保证判断和new是原子的
                if(singletons==null){   
                    singletons=new Singletons();
                }
            }
        }
        return singletons;
    }

    private Singletons(){};
}
public class ThreadDome2 {
    public static void main(String[] args) {
        Singletons s1=Singletons.getSingletons();
        Singletons s2=Singletons.getSingletons();
        System.out.println(s1 == s2);
    }
}

关于这个代码中是否是涉及到了内存可见性是存在疑问的,但是管他有没有问题,volatile不管他有没有问题,都一起解决了~

总结:

如何解决懒汉模式的线程安全问题?

1.加锁,把if和new变成原子的

2.双重if来减少不必要的加锁

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


总结

本文介绍了单例模式的概念和创建一个简单的单例模式,实现单例模式经典的两个方式,如果解决懒汉模式的线程安全问题~。最后感谢老铁支持~

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值