【多线程】(基础三)

多线程(基础三)

单例模式

🔑单例模式是设计模式之一,那设计模式又是啥呢?设计模式就类似于棋谱,是前人写代码时总结出的一些固定的套路,单例模式指是某个类在程序中只存在唯一的实例,不会创建出多个实例。单例模式有两种实现方式:饿汉模式,懒汉模式

饿汉模式

饿汉模式是程序一启动就创建了实例,实例创建的早

class Singleton{
   
    private static Singleton singleton = new Singleton();

    public static Singleton getInstance(){
   
        return singleton;
    }
    private Singleton(){
   
        
    }
}

✅类属性引用了一个实例对象,(类属性在了类对象里,而类对象在整个程序中只有一份,所以类属性就只有一份)。当 程序一启动,这个Singleton类就会被加载,就会随之创建一个类对象,那类属性又在类对象里,所以也就是,当程序一启动,类属性就被初始化,singleton就引用了Singleton实例,正因为实例创建的比较早,所以叫饿汉模式

✅把构造方法设为私有的,保证类外无法创建实例,而类属性只有一份,那Singleton实例只有一份,这样就实现了单例模式(只能创建一个实例)

懒汉模式(单线程版)

懒汉模式是第一次用的啥时候再创建实例,一直不用就不创建,这就是懒汉模式

class SingletonLazy{
   
    private static SingletonLazy singletonLazy = null;
    
    public static SingletonLazy getInstace(){
   
        if(singletonLazy==null){
   
            singletonLazy = new SingletonLazy();
        }
        return singletonLazy;
    }
    
    private SingletonLazy(){
   
        
    }
}

✅第一次用的时候创建实例,以后再用直接返回。类加载的时候不创建实例,调用方法的时候再创建,这就是懒汉模式

懒汉模式(多线程版)

思考:上面两种单例模式如果在多线程环境下会出现线程安全问题吗?

✅对于饿汉模式每次调用方法都是读操作,所以不会出现线程安全问题,但是对于懒汉模式,在多线程环境中,下面代码中的if语句和new语句,两个线程可能会同时执行,当其中一个线程判断if条件成立,new对象之前,另一个线程也判断if成立,然后也new对象,这样两个线程就new了两个对象,造成了线程安全问题。

 public static SingletonLazy getInstace(){
   
        if(singletonLazy==null){
   
            singletonLazy = new SingletonLazy();
        }
        return singletonLazy;
    }

改进版1:

✅造成线程安全问题的原因主要是if语句和new对象语句不是原子操作,一个线程在执行时,另一个线程也可以执行。 所以解决方式就是加锁,让if语句(读操作)和new对象语句(写操作)是一个原子操作。这样两个线程执行同步代码块就有了先后顺序。这样就不会产生new出两个对象这样的bug了

class SingletonLazy{
   
    private static SingletonLazy singletonLazy = null;

    public static SingletonLazy getInstace(){
   
        synchronized (Singleton.class){
   
            if(singletonLazy==null){
   
                singletonLazy = new SingletonLazy();
            }
        }
        return singletonLazy;
    }

    private SingletonLazy(){
   

    }
}

改进版2:

✅虽然加锁解决了这样的一个问题,但是又产生了一个新问题,那就是只有当第一次初始化的时候可能会产生线程安全问题,等实例创建好之后再调用getInstance()就不会产生线程安全问题了,但是按上面代码,每次调用方法,都要执行同步代码块,也就是每次都要加锁释放锁(加锁释放锁就会产生较大的时间开销),这样就很不合理,创建好实例之后再调用方法就不会产生线程安全问题了,也就没必要在加锁释放锁了,直接返回实例就行了,看下面的改进版

class SingletonLazy{
   
    private static SingletonLazy singletonLazy = null;

    public static SingletonLazy getInstace(){
   
        
        if(singletonLazy==null){
   
            synchronized (Singleton.class){
   
                if(singletonLazy == null){
   
                    singletonLazy = new SingletonLazy();
                }
            }
        }
        return singletonLazy;
    }

    private SingletonLazy(){
   

    }
}

✅外层if判定是否已经初始化好了,如果初始化好了,就直接返回,如果没有初始化,尝试加锁然后尝试初始化,内层if是判定当前线程拿到锁之后,再判定一下是否真的要进行初始化

✅如果多个线程并发执行,只有其中一个线程能先拿到锁,其余线程顶多执行到synchronized (Singleton.class)就阻塞了,当第一个拿到锁的线程创建完对象释放锁后,其余线程如果还没进入外层if那就直接返回,如果其余线程进入了外层if,就能尝试加锁,然后执行代码,但是由于第一个线程创建了对象,其余线程就不能创建对象了,所以再用内层的if判定一下是否真的需要创建线程。


public static SingletonLazy getInstace(){
   
        if(singletonLazy==null){
   
            synchronized (Singleton.class){
   
                if(singletonLazy == null){
   
                    singletonLazy = new SingletonLazy();
                }
            }
        }
        return singletonLazy;
    }

改进版3:

上面方法中,有的是读操作,有的是写操作,如果一个线程把数据读到寄存器了,然后另一个线程会不会直接复用寄存器中的值呢,也就是会不会因为编译器优化导致内存可见性问题呢?这个是不确定的,按理来说每个线程有自己的寄存器,不会出现这种复用的情况,但是编译器优化不好说从哪个角度优化,不好说会不会出现复用这种情况,所以加上volatile关键字,禁止编译器优化,是比较稳健的

class SingletonLazy{
   
    volatile private static SingletonLazy singletonLazy = null;

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值