JavaEE-多线程单例模式

单例模式是校招中最常考的 设计模式之⼀
啥是设计模式?
设计模式好⽐象棋中的 "棋谱". 红⽅当头炮, ⿊⽅⻢来跳. 针对红⽅的⼀些⾛法, ⿊⽅应招的时候有⼀些固定的套路. 按照套路来⾛局势就不会吃亏.
软件开发中也有很多常⻅的 "问题场景". 针对这些问题场景, ⼤佬们总结出了⼀些固定的套路. 按照这个套路来实现代码, 也不会吃亏.

单例模式能保证某个类在程序中只存在唯一一份实例,而不会创建出多个实例。

这一点在很多场景上都需要,比如JDBC中的DataSource实例就只需要一个 

 单例模式具体的实现⽅式有很多. 最常⻅的是 "饿汉" 和 "懒汉" 两种.

饿汉模式

类加载的同时, 创建实例.
 
class Singleton{
    private static Singleton instance = new Singleton();//()内可加数字//静态成员的初始化是在类加载的阶段触发的

    public static Singleton getInstance(){
        return instance;
    }

    private Singleton(){

    }//单例模式中的 点睛之笔 在类外进行new操作都会编译失败


    private Singleton(int n){

    }
}

public class Main {
    public static void main(String[] args) {
        Singleton t1 = Singleton.getInstance();
        Singleton t2 = Singleton.getInstance();
        System.out.println(t1 == t2);

        //Singleton t3 = new Singleton();
    }
}

输出结果如下: 

 5078c80605aa4118a74d8bc59dbb49a0.png

懒汉模式-单线程版

类加载的时候不创建实例. 第⼀次使⽤的时候才创建实例.
class SingletonLazy{
    private static SingletonLazy instance = null;

    private SingletonLazy(){

    }

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

public class Demo29 {
    public static void main(String[] args) {
        SingletonLazy s1 = SingletonLazy.getInstance();
        SingletonLazy s2 = SingletonLazy.getInstance();
        System.out.println(s1 == s2);

        
    }
}

懒汉模式-多线程版

上⾯的懒汉模式的实现是线程不安全的.
线程安全问题发⽣在⾸次创建实例时. 如果在多个线程中同时调⽤ getInstance ⽅法, 就可能导致创建出多个实例.
⼀旦实例已经创建好了, 后⾯再多线程环境调⽤ getInstance 就不再有线程安全问题了(不再修改instance 了)

 加上 synchronized 可以改善这⾥的线程安全问题。

class Singleton {
     private static Singleton instance = null;
     private Singleton() {}
     public static SingletonLazy getInstance3(){

            synchronized (locker){//当把实例创建好后,后续再调用getInstance,此时都是直接执行return
                if (instance == null){//但是,每次调用上述的方法,都会触发一次加锁操作,虽然不涉及线性安全问题
                    instance = new SingletonLazy();//多线程情况下,这里的加锁就会相互阻塞,影响程序运行效率
                }
            }

        return instance;
    }

    public static SingletonLazy getInstance2(){
        if (instance == null){
            synchronized (locker){

                instance = new SingletonLazy();

            }
        }
        return instance;
    }

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

以上有3种方法,不过还是存在一定的BUG!!!

懒汉模式-多线程版(改进)

以下代码在加锁的基础上, 做出了进⼀步改动:
• 使⽤双重 if 判定, 降低锁竞争的频率.
 
• 给 instance 加上了 volatile.
class SingletonLazy{
    private static volatile SingletonLazy instance = null;//预防内存可见性问题
    //volite的功能: 确保每次读取操作都是读内存  关于该变量的读取和修改操作不会触发重排序
    //private static SingletonLazy instance = null;
    private static Object locker = new Object();

    private SingletonLazy(){

    }

    public static SingletonLazy getInstance(){//优化
        if (instance == null){//这个if是判断是否需要加锁
            synchronized (locker){
                if (instance == null){//这个if是判断是否需要new 对象
                    instance = new SingletonLazy();
                }
            }
        }
        return instance;
    }
}
理解双重 if 判定 / volatile:
加锁 / 解锁是⼀件开销⽐较⾼的事情. ⽽懒汉模式的线程不安全只是发⽣在⾸次创建实例的时候. 因此后续使⽤的时候, 不必再进⾏加锁了.
外层的 if 就是判定下看当前是否已经把 instance 实例创建出来了.
同时为了避免 "内存可⻅性" 导致读取的 instance 出现偏差, 于是补充上 volatile .
当多线程⾸次调⽤ getInstance, ⼤家可能都发现 instance 为 null, 于是⼜继续往下执⾏来竞争锁, 其 中竞争成功的线程, 再完成创建实例的操作.
当这个实例创建完了之后, 其他竞争到锁的线程就被⾥层 if 挡住了. 也就不会继续创建其他实例.
1. 有三个线程, 开始执⾏ getInstance , 通过外层的 if (instance == null) 知道了实例还没有创建的消息. 于是开始竞争同⼀把锁.
42358b82e29e41bb98f7b02d742b2642.png
2. 其中线程1 率先获取到锁, 此时线程1 通过⾥层的 if (instance == null) 进⼀步确认实例
是否已经创建. 如果没创建, 就把这个实例创建出来.
6a216a4fff894e8bb06e135057ddb2d7.png
3. 当线程1 释放锁之后, 线程2 和 线程3 也拿到锁, 也通过⾥层的 if (instance == null) 来
确认实例是否已经创建, 发现实例已经创建出来了, 就不再创建了.
6d5cf80709e542f399e4ebb062b97f80.png
4. 后续的线程, 不必加锁, 直接就通过外层 if (instance == null) 就知道实例已经创建了,
从⽽不再尝试获取锁了. 降低了开销.
c9166836343e46deb7ae53e32b96827d.png

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值