多线程中的单例模式

1. 单例模式

单例模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象,也就是说不能使用new关键字来创建对象。

2. 饿汉模式和懒汉模式回顾

单例模式实现的方式很多,主要分为两个大类:饿汉模式和懒汉模式。这里我们简单回顾一下饿汉模式和懒汉模式的实现方式。

2.1 饿汉模式

先来简单回顾下饿汉模式:其实就是使用静态变量创建实例(唯一性),并且让构造方法设为私有(堵住new实例的口子),当SingleTon类被第一次加载的时候就会创建这么一个实例,因此称其为饿汉模式。

设置一个类属性instance,保证在类加载的时候就创建实例对象instance,并且设置一个getInstance方法来获取该实例:

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

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

将构造方法设为私有,其他的类就无法new一个新的实例了:

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

    public static SingleTon getInstance() {
        return instance;
    }
    
    private SingleTon() {}
}

测试代码:

public class Demo17 {
    public static void main(String[] args) {
        SingleTon instance1 = SingleTon.getInstance();
        SingleTon instance2 = SingleTon.getInstance();
        System.out.println("是否是同一个实例:" + (instance1 == instance2));
    }
}

运行结果:

是否是同一个实例:true

Process finished with exit code 0

2.2 懒汉模式

先来简单回顾下懒汉模式:不着急实例化,当第一次调用getInstance()的时候再实例化对象。

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

    private SingletonLazy(){}
}

测试代码:

public class Demo17 {
    public static void main(String[] args) {
        SingletonLazy instance1 = SingletonLazy.getInstance();
        SingletonLazy instance2 = SingletonLazy.getInstance();
        System.out.println("是否为同一个实例:" + (instance1 == instance2));
    }
}

运行结果:

是否为同一个实例:true

Process finished with exit code 0

3. 多线程中的单例模式

上述的两个单例模式代码,在多线程的环境下,调用getInstance()是安全的吗?

对于饿汉模式,只涉及多线程的,多个线程读是线程安全的。

对于懒汉模式,涉及多线程的读和写,这就是线程不安全的,一旦实例创建好后,if条件就进不去了,此时也就是读操作了,也就线程安全了。

与前面讲过的,一个线程只负责写,一个线程只负责读的情况不同。

在懒汉模式这个场景,涉及到多个线程读+写操作,如果只对写操作进行加锁:

public static SingletonLazy getInstance() {
    if (instance == null) {
        //加锁操作
        synchronized (SingletonLazy.class) {
            instance = new SingletonLazy();
        }
    }
    return instance;
}

可能会出现:两个线程在写之前同时都执行到了LOAD的操作,两个线程都发现instancenull,于是都去进行写操作。

因此需要给读+写操作一起加锁:

public static SingletonLazy getInstance() {
    synchronized (SingletonLazy.class) {
        if (instance == null) {
            //加锁操作
            instance = new SingletonLazy();
        }
    }
    return instance;
}

但是,懒汉模式只有在实例未创建的情况下,才会有线程安全问题。也就是说一旦实例创建好了,就不再有线程安全问题了,既然如此,后续在调用getInstance的时候就不应该去加锁了。

由于锁竞争是需要消耗资源、影响性能的,因此我们直接在synchronize代码带外层再加上一层if判定语句:

public static SingletonLazy getInstance() {
    if (instance == null) {
        synchronized (SingletonLazy.class) {
            if (instance == null) {
                //加锁操作
                instance = new SingletonLazy();
            }
        }
    }
    return instance;
}

变量instance是否会存在内存可见性问题呢?

在多核CPU上运行的多线程程序中,每个线程都可能在不同的CPU核心上执行,每个CPU核心都有自己的缓存(包括寄存器、高速缓存等)。这就导致了一个问题,即当一个线程修改了某个变量的值时,该变量的值可能会被缓存在该线程所在的CPU核心的缓存中,而不会立即写回到主内存中。如果其他线程在不同的CPU核心上访问相同的变量,它们可能会读取到自己CPU核心缓存中的旧值,而不是最新的值,这就引发了内存可见性问题,因此还需要给instance加上一个volatile关键字。

最终代码如下:

class SingletonLazy {
    volatile private static SingletonLazy instance = null;
    public static SingletonLazy getInstance() {
        if (instance == null) {
            synchronized (SingletonLazy.class) {
                if (instance == null) {
                    //加锁操作
                    instance = new SingletonLazy();
                }
            }
        }
        return instance;
    }

    private SingletonLazy(){}
}
  • 5
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

干脆面la

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

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

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

打赏作者

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

抵扣说明:

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

余额充值