单列模式及不安全分析

单列模式

1)饿汉式

加载的时候就实例化,并且创建单例对象

public class Hungry{
   
    //声明为静态,类加载的时候就实例化,并且创建单例对象
    private static final Hungry hungry=new Hungry();
    private Hungry(){}
    public static Hungry getInstance(){
        return hungry;
    }
}

2)懒汉式

懒汉单例模式:单线程

public class SingletonDemo {

    private static SingletonDemo instance = null;

    private SingletonDemo () {
        System.out.println(Thread.currentThread().getName() + "\t 我是构造方法SingletonDemo");
    }

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

    public static void main(String[] args) {
        // 这里的 == 是比较内存地址
        System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());
        System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());
        System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());
        System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());
    }
}

输出结果:

main    我是构造方法singletonDemo
true
true
true
true

但是,懒汉式在多线程环境下可能存在安全问题

public class SingletonDemo {

    private static SingletonDemo instance = null;

//构造方法私有化是为了不让外部多次调用构造方法
    private SingletonDemo () {
        System.out.println(Thread.currentThread().getName() + "\t 我是构造方法SingletonDemo");
    }

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

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                SingletonDemo.getInstance();
            }, String.valueOf(i)).start();
        }
    }
}

输出结果:

4	 我是构造方法SingletonDemo
2	 我是构造方法SingletonDemo
5	 我是构造方法SingletonDemo
6	 我是构造方法SingletonDemo
0	 我是构造方法SingletonDemo
3	 我是构造方法SingletonDemo
1	 我是构造方法SingletonDemo

显然不能保证单例。

解决方法之一:用synchronized修饰方法getInstance(),但它属重量级同步机制,使用时慎重。

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

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

比上一个缩小了锁的粒度,但是每个线程都需要等待并判断是否==null

解决方法之二:DCL(Double Check Lock双端检锁机制)

public class SingletonDemo{
	private SingletonDemo(){}
    
    private volatile static SingletonDemo instance = null;

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

//加锁之前加锁之后都判断一次,减小锁力度,缩短阻塞时间
//如果只保证单例的情况下,第一个if可以不要,第一个if是为了保证效率
//第一次判空就是优化性能,单例模式第一次启动的时候会创建对象,以后的线程就永远不会走到第二个判空了,,只有第一次创建的时候才会锁住,性能提升了很多

DCL中解析volatile存在的问题解析
原因在于某一个线程执行到第一次检测,读取到的instance不为null时,instance的引用对象可能没有完成初始化。instance = new SingletonDemo();可以分为以下3步完成(伪代码):

memory = allocate(); //1.分配对象内存空间instance(memory); //2.初始化对象instance = memory; //3.设置instance指向刚分配的内存地址,此时instance != null

步骤2和步骤3不存在数据依赖关系,而且无论重排前还是重排后程序的执行结果在单线程中并没有改变,因此这种重排优化是允许的。

memory = allocate(); //1.分配对象内存空间instance = memory;//3.设置instance指向刚分配的内存地址,此时instance! =null,但是对象还没有初始化完成!instance(memory);//2.初始化对象

但是指令重排只会保证串行语义的执行的一致性(单线程),但并不会关心多线程间的语义一致性。
所以当一条线程访问instance不为null时,由于instance实例未必已初始化完成,也就造成了线程安全问题。

指令重排导致指针指向了分配的内存空间,这里已经分配内存之后堆空间的基础属性是有默认值的,只是还没执行构造函数的初始化,调用类内部是引用的话可能会空指针,基础类型的话会返回初始值

问题:上述指定重排序造成的空指针异常怎么解决

参考资料:
https://www.bilibili.com/video/BV1zb411M7NQ?from=search&seid=995150543463167773&spm_id_from=333.337.0.0

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值