双重检验锁方式实现单例模式

单例模式(Singleton Pattern)是指在内存中只会创建且仅创建一次对象的设计模式。在程序中多次使用同一个对象且作用相同时,为了防止频繁地创建对象使得内存飙升,单例模式可以让程序仅在内存中创建一个对象,让所有需要调用的地方都共享这一单例对象。

单例模式有两种类型:

        1,饿汉式:就是说我很饿看到啥都吃,也就是在类加载的时候就创建给对象。

        2,懒汉式:不是很饿,当真正用到该对象的时候才进行创建。

单例模式注意

        1,单例类只能有一个实例

        2,单例类必须自己创建自己的唯一实例(对象不能new出来)

        3,单例类必须给所有其他对象提供自己创建好的实例

1,对应Java代码实现-饿汉式

//饿汉单例
public class HungrySingle {
    //构造器私有
    private HungrySingle(){
        
    }
    //创建自己的唯一实例
    private static final HungrySingle HUNGRY_SINGLE=new HungrySingle();
    //对外提供自己的这唯一实例
    private static HungrySingle getInstance(){
        return HUNGRY_SINGLE;
    }
}

2,对应Java代码实现-懒汉式

//懒汉单例
public class LazySingle {
    //构造器私有
    private LazySingle(){

    }
    private static LazySingle LAZY_SINGLE;
    private static LazySingle getInstance(){
        //当前实例没有被创建的时候才行创建
        if(LAZY_SINGLE==null){
           LAZY_SINGLE=new LazySingle();
        }
        return LAZY_SINGLE;
    }
}

3,双重检验锁方式实现单例模式   

上述的懒汉单例实现是不完美的,因为我们创建实例的时候需要先判断是否为空,单如果实在多线程环境下,同时判断这个实例对象为空于是就创建了不同的对象,测试如下:

package com.qmlx.springbootinit.Pattern;
//懒汉单例
public class LazySingle {
    //构造器私有
    private LazySingle(){
        System.out.println(Thread.currentThread().getName()+"线程创建了对象");
    }
    private static LazySingle LAZY_SINGLE;
    private static LazySingle getInstance(){
        //当前实例没有被创建的时候才行创建
        if(LAZY_SINGLE==null){
           LAZY_SINGLE=new LazySingle();
        }
        return LAZY_SINGLE;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                LazySingle instance = LazySingle.getInstance();
                System.out.println(instance);
            }).start();
        }
    }
}

代码执行结果如下:

 根据输出以及当前创建的对象,我们可以看到他创建了不同的对象,所以,为了解决这个方法,我们在创建对象的时候使用 synchronized关键字包裹  这样我们就能保证创建对象的操作式互斥的,从而保证对象的单例,这就是常说的双重检验锁方式实现单例模式

代码如下:

//懒汉单例
public class LazySingle {
    //构造器私有
    private LazySingle(){
        System.out.println(Thread.currentThread().getName()+"线程创建了对象");
    }
    private static LazySingle LAZY_SINGLE;
    private static LazySingle getInstance(){
        //当前实例没有被创建的时候才行创建
        if(LAZY_SINGLE==null){
            synchronized (LazySingle.class){
                if(LAZY_SINGLE==null){
                    LAZY_SINGLE=new LazySingle();
                }
            }
        }
        return LAZY_SINGLE;
    }
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                LazySingle instance = LazySingle.getInstance();
                System.out.println(instance);
            }).start();
        }
    }

}

但是他依旧存在问题,继续往下看。

LAZY_SINGLE=new LazySingle();

这段代码并不是原子性的,因为这段代码其实是分为三部分来执行的:

1,为LAZY_SINGLE分配内存空间

2,初始化LAZY_SINGLE,也就是执行构造方法去初始化这个对象

3,将LAZY_SINGLE指向分配的内存地址

正常步骤是1->2->3,但是JVM具有指令重排序的特性,在单线程下是不会存在问题的,但是在多线程下,会导致一个线程拿到还没有进行初始化的实例,例如线程1执行了1->3,然后线程二获取这个实例,发现不为空,拿到但是结果是没有初始化的。

4,如何防止指令重排序呢?

我们可以使用volatile关键字,他有两个关键作用:

1,保证线程间共享变量的可见性(防止了JIT对共享变量的优化),例如

你写的代码------------------>JIT优化之后的代码

 

所以,我们对变量添加volatile关键字就是告诉JIT编译器,我这个变量你不要优化。

2,防止指令重排序(JVM对程序执行中的优化)

所以我们对当前变量添加volatile关键字,当对这个变量进行读写操作的时候会通过擦汗如特定的内存屏障的方式来禁止指令重排序

具体如下(我的笔记,不知道可不可以帮助理解)

最终代码如下:

//懒汉单例
public class LazySingle {
    //构造器私有
    private LazySingle(){
        System.out.println(Thread.currentThread().getName()+"线程创建了对象");
    }
    private static volatile LazySingle LAZY_SINGLE;
    private static LazySingle getInstance(){
        //当前实例没有被创建的时候才行创建
        if(LAZY_SINGLE==null){
            synchronized (LazySingle.class){
                if(LAZY_SINGLE==null){
                    LAZY_SINGLE=new LazySingle();
                }
            }
        }
        return LAZY_SINGLE;
    }

}

 其实这种实现方式也不是完美的,因为Java中那可是存在反射的,他就可以破坏对象的单例!

具体如何???

等我深刻理解之后,在谈!!!!

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值