多线程单例模式(饿汉模式与懒汉模式)

单例模式

单例模式(Singleton)是一种常用的软件设计模式,属于创建类型的一种。它确保一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。单例模式的主要目标是提供一个全局的访问点。

什么是设计模式? 

设计模式就好比象棋,围棋中的"棋谱"。在"棋谱"中,大佬们把常见的对局情景给推演出来了,针对红方的一些走法,黑方应招的时候有 一些固定的套路,按照套路来走局势就不会吃亏。

软件开发中也有很多常见的 "问题场景",针对这些问题场景,,大佬们总结出了一些固定的套路, 按照这个套路来实现代码,也不会吃亏。

单例模式的两种实现方式

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

饿汉模式

饿汉模式是在类加载过程中就把实例创建出来了。之所以叫"饿汉",因为饿了很久的人看到食物就会非常急切,而这种在类加载过程中就把实例创建出来了就给人一种非常"急切"的感觉。

我们就用代码来描述一下饿汉:

class Singleton{
    // 在此处, 先把这个实例给创建出来了.
    private static Singleton instance = new Singleton();
    //getInstance()方法用于获取Singleton类的唯一实例
    public static Singleton getInstance(){
        return  instance;
    }
    private Singleton(){

    }
}

但是这又是如何保证实例唯一呢?

不妨写下Main方法,在Main方法中这样是会报错。

public class Test {
    public static void main(String[] args) {
        Singleton s =new Singleton(); 
        System.out.println(s1 == s2);
    }
}

因为前面我们已经将构造方法设为private,这样在类外就无法通过 new 的方式来创建这个 Singleton 实例。我们通过调用getInstance方法就能获取实例。


public class Test {
    public static void main(String[] args) {
        //Singleton s =new Singleton(); 
        Singleton s1 = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();
        System.out.println(s1 == s2);
    }
}

这样就能保证实例唯一。

完整运行一下结果

最后完整代码:

class Singleton{
    // 在此处, 先把这个实例给创建出来了.
    private static Singleton instance = new Singleton();
    //getInstance()方法用于获取Singleton类的唯一实例
    public static Singleton getInstance(){
        return  instance;
    }
    // 为了避免 Singleton 类不小心被复制出多份来.
        // 把构造方法设为 private. 在类外面, 就无法通过 new 的方式来创建这个 Singleton 实例了!!
    private Singleton(){

    }
}
public class Test {
    public static void main(String[] args) {
    //Singleton s =new Singleton();  在类外面, 就无法通过 new 的方式来创建这个 Singleton 实例
        Singleton s1 = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();
        System.out.println(s1 == s2);
    }
}

懒汉模式

懒汉模式是在类加载的时候不会创建实例,而是第一次使用时才会创建实例。

我们还是用代码来描述一下懒汉:

class SingletonLazy {
    volatile private static SingletonLazy instance = null;

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

    }
}

再写下Main方法来试试:

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

最后运行结果为

完整代码如下:

class SingletonLazy {
    volatile private static SingletonLazy instance = null;

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

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

饿汉模式和懒汉模式中的线程安全问题

上面描述的饿汉模式与懒汉模式如果是在多线程情况下调用getInstance方法,是否会出现线程安全问题呢?

在饿汉模式中,在类加载时就完成了初始化,getInstance直接返回instance实例,这个操作本质上就是"读操作",那么多个线程读取同一个变量是线程安全的。

new操作的本质也是多个指令。在懒汉模式中,t1线程先行判断instance是否为null,随后切换到t2线程,也进行了一次判断,且new了一个新的实例出来,最后继续执行t1线程,由于之前判断的条件仍然满足,所以这里同样会new一个新的实例。这样就"既有读又有写",这样就会出现线程安全问题。

如何让懒汉模式 能够成为线程安全的呢?

没错!!加锁

 此时已经使用synchronized对new操作加锁

这段代码中还有一个问题:加锁操作是有开销的,我们真的需要每次都加锁吗?

 这里的加锁只是在new出对象之前加锁,这是有必要的。但是一旦new完对象,后续调用getInstance,此时的instance值是非空的,因此会触发return。这俩操作都相当于读操作不加锁也没关系

 所以,如果对象还没创建才加锁,如果对象已经创建就不用加锁了。

 但是这个代码还有问题!

 内存可见性问题指令重排序问题

 内存可见性问题:假设多个线程都去进行getIntance,这个时候就会有被优化的风险。

 指令重排序问题

instance = new SingletonLazy();

可以拆分为三个步骤:

1.申请内存空间

2.调用构造方法,把这个内存空间初始化成一个合理的对象

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

正常情况下,按照1 2 3顺序来执行

但是编译器为了提高程序效率,会发生指令重排序,即调整代码的执行顺序,1 2 3这个顺序可能会变成 2 1 3 

 所以!! 我们要使用volatile 这个关键字来 解决这两个问题。

这样懒汉模式的线程安全问题就解决了。

以下是完整代码:

package Thread;
//懒汉模式
class SingletonLazy {
    volatile private static SingletonLazy instance = null;

    public static SingletonLazy getInstance() {
        //使用双重 if 判定, 降低锁竞争的频率.
        //给 instance 加上了 volatile.
        if (instance == null) {
            synchronized (SingletonLazy.class) {
                if (instance == null) {
                    instance = new SingletonLazy();
                }
            }
        }
        return instance;
    }
    private SingletonLazy(){

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

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值