【Java】多线程-单例模式/volatile-指令重排序

单例模式即代码中只有一个实例的模式

适用场景:有些场景下,有的类只能有一个对象,不能有多个

要注意:在单例模式下,要保证不能产生多个实例

1、饿汉模式

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

    public static Singleton getSingleton() {
        return instance;
    }

    private Singleton(){
    }
}

public class demo {
    public static void main(String[] args) {
        Singleton singleton1 = Singleton.getSingleton();
        Singleton singleton2 = Singleton.getSingleton();
        System.out.println(singleton1==singleton2);
        System.out.println(singleton1.equals(singleton2));
    }
}

将构造方法设为private,使得不能自己初始化类, 只能用默认给定的类

由于是单例,所以singleton1与singleton2地址内容都是一样的,两者是同一个实例

2、 懒汉模式

(1)懒汉模式-不安全

class SingletonLazy{
    private static SingletonLazy instance = null;

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

    private SingletonLazy(){
    }
}

public class demo {
    public static void main(String[] args) {
        SingletonLazy singletonLazy1 = SingletonLazy.getSingletonLazy();
        SingletonLazy singletonLazy2 = SingletonLazy.getSingletonLazy();
        System.out.println(singletonLazy1 == singletonLazy2);
        System.out.println(singletonLazy1.equals(singletonLazy2));
    }
}

饿汉模式中,无论原来有没有创建instance,都会初始化一个新的instance

懒汉模式为了节约开销,会在getSingletonLazy()方法中判断instance是否是null。如果不是null,说明instance已经初始化,只须直接返回即可;如果是第一次获取,那么instance没有初始化,是null,这时初始化后再返回

(2)懒汉模式-线程安全

上面的代码存在的问题是:由于判断if(instance == null)这一步和初始化instance = new SingletonLazy()这一步是分开的,有可能会出现线程安全问题

​​​​​​​

如上图,T1和T2都各创建了一个instance实例,从而出现了两个实例,破坏了单例模式的规则

为了解决上述线程安全问题,我们在getSingletonLazy()方法中对这段代码进行加锁(静态方法代码块加锁对象用类名.class)

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

(3)空间浪费

改成加锁代码后,产生了一个新问题,那就是:每次获得instance时都要进行加锁,但是加锁本身是一项耗费空间资源的操作,这样便会大大降低代码性能

如果我们能够得知实例已经创建,那么就不用再进行加锁了,所以在加锁之前我们对instance进行一次判断

public static SingletonLazy getInstance() {
        if(instance == null){
            synchronized (SingletonLazy.class){
                if(instance == null){
                    instance = new SingletonLazy();
                }
            }
        }

        return instance;
}

注意,第一次判断和第二次判断作用并不一样:第一次是为了判断是否需要加锁;第二次是为了判断是否需要新创建实例

(4)指令重排序问题

经过两次优化,上述代码仍存在问题,那就是——指令重排序问题

 · 什么是指令重排序问题呢

当我们在new一个对象的时候,new这个过程分为三步

1.申请内存空间
2.在内存空间上构造对象 (构造方法)

3.把内存的地址,赋值给 instance 引用

这个过程可以按照123来执行,也可以按照132的顺序来执行 

在我们上述优化过的代码中,如果T1线程在new的过程中按照132的顺序来执行,那么在执行到第二步时,恰好T2第一次对instance进行判断,由于instance已经创建了实例,那么T2会直接返回这个没有构造对象的instance

如果代码中对返回的instance进行访问属性/方法等操作,程序就会报错

 · volatile

为了解决这个问题,我们使用了volatile关键字来对instance变量进行修饰

这样,在进行new操作时,就不会产生指令重排序问题

class SingletonLazy{
    private static volatile SingletonLazy instance = null;

    public static SingletonLazy getInstance() {
        if (instance == null){
            synchronized (SingletonLazy.class){
                if (instance == null){
                    instance = new SingletonLazy();
                }
            }
        }
        return instance;
    }
    
    private SingletonLazy(){
    }
}

volatile解决内存可见性问题

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

沙河板混

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值