线程安全的单例模式

1.啥是设计模式?
设计模式好比象棋中的 "棋谱". 红方当头炮, 黑方马来跳. 针对红方的一些走法, 黑方应招的时候有
一些固定的套路. 按照套路来走局势就不会吃亏.软件开发中也有很多常见的 "问题场景". 针对这些问题场景, 大佬们总结出了一些固定的套路. 按照这个套路来实现代码, 也不会吃亏.

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

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

单例模式具体的实现方式, 分成 "饿汉" 和 "懒汉" 两种.

2.饿汉模式

类加载的同时, 创建实例.

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

    private Singleton() {
    }

    public static Singleton getInstance() {
        return instance;
    }
}

3.懒汉模式

类加载的时候不创建实例. 第一次使用的时候才创建实例.

class Singleton {
    private static Singleton instance = null;

    private Singleton() {
    }

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

package thread3;

//单例模式
//饿汉模式
class SingletonDataSource {
    private static SingletonDataSource instance = new SingletonDataSource();

    //外面的类无法调用该类的构造方法(别的类无法创建实例了)
    private SingletonDataSource() {

    }

    //一方面把程序中要用到的实例准备好
    //一方面把外部构造实例的口子给封上

    //留下一个通道,让外界能访问到 instance 这个唯一对象 (外界访问该实例的唯一通道)
    public static SingletonDataSource getInstance() {
        return instance;
    }
}

//懒汉模式
//和饿汉模式相比,懒汉模式,主要的差别就在于这个实例的创建时机不同了,不在是类加载的时候就立即创建实例
//而是在首次调用到getInstance的时候,才会真正创建实例
class SingletonDataSource2 {
    private static SingletonDataSource2 instance = null;

    private SingletonDataSource2() {

    }

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

public class Test9 {
    public static void main(String[] args) {
        //无论在代码中的哪个地方来调用这里的getInstance得到的都是同一个实例
        SingletonDataSource dataSource = SingletonDataSource.getInstance();
    }
}

4.饿汉模式-懒汉模式 线程安全问题

对于饿汉模式来说,多线程调用getInstance,只是针对同一个变量来“读”,线程是安全的!

但是对于懒汉模式来说,多线程调用getInstance,大部分情况是读,但是会修改内容,这就会导致线程不安全!

4.懒汉模式-多线程版

上面的懒汉模式的实现是线程不安全的

线程安全问题发生在首次创建实例时. 如果在多个线程中同时调用 getInstance 方法, 就可能导致创建出多个实例.
一旦实例已经创建好了, 后面再多线程环境调用 getInstance 就不再有线程安全问题了(不再修改instance 了)

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

class Singleton {
    private static Singleton instance = null;

    private Singleton() {
    }

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

5.懒汉模式-多线程版(改进,终极版)

以下代码在加锁的基础上, 做出了进一步改动:

  • 使用双重 if 判定, 降低锁竞争的频率.
  • 给 instance 加上了 volatile.
class Singleton {
    private static volatile Singleton instance = null;

    private Singleton() {
    }

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


理解双重 if 判定 / volatile:

加锁 / 解锁是一件开销比较高的事情. 而懒汉模式的线程不安全只是发生在首次创建实例的时候.因此后续使用的时候, 不必再进行加锁了.外层的 if 就是判定下看当前是否已经把 instance 实例创建出来了.同时为了避免 "内存可见性" 导致读取的 instance 出现偏差, 于是补充上 volatile .当多线程首次调用 getInstance, 大家可能都发现 instance 为 null, 于是又继续往下执行来竞争锁,其中竞争成功的线程, 再完成创建实例的操作.当这个实例创建完了之后, 其他竞争到锁的线程就被里层 if 挡住了. 也就不会继续创建其他实例.

5.写一个线程安全的单例模式(终极版)

package thread3;

//单例模式
//饿汉模式
class SingletonDataSource {
    private static SingletonDataSource instance = new SingletonDataSource();

    //外面的类无法调用该类的构造方法(别的类无法创建实例了)
    private SingletonDataSource() {

    }

    //一方面把程序中要用到的实例准备好
    //一方面把外部构造实例的口子给封上

    //留下一个通道,让外界能访问到 instance 这个唯一对象 (外界访问该实例的唯一通道)
    public static SingletonDataSource getInstance() {
        return instance;
    }
}

//懒汉模式
//和饿汉模式相比,懒汉模式,主要的差别就在于这个实例的创建时机不同了,不在是类加载的时候就立即创建实例
//而是在首次调用到getInstance的时候,才会真正创建实例
class SingletonDataSource2 {
    private volatile static SingletonDataSource2 instance = null;

    private SingletonDataSource2() {

    }

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


public class Test9 {
    public static void main(String[] args) {
        //无论在代码中的哪个地方来调用这里的getInstance得到的都是同一个实例
        SingletonDataSource dataSource = SingletonDataSource.getInstance();
    }
}


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

阿瞒有我良计15

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

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

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

打赏作者

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

抵扣说明:

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

余额充值