Java并发编程-单例模式与线程安全性问题

时间:2022-07-24 13:11:26

1.饿汉模式

public class Singleton {

private static Singleton instance = new Singleton();

private Singleton() {

        }

public static Singleton getInstance() {
return instance;
}

}

这种单例模式在类初始化的时候就会实例化,不管是否会立即使用该实例,这对于大系统来说会加重初始化的负担,所以就有了懒汉式单例模式。看似没有什么问题,但如果单例类还扮演了其他角色比如说还对外提供了一个public static String get() {return "something"}方法,则会被再次初始化,违背了单例的原则。为了解决这个问题,引入了延迟加载机制。

2.懒汉式模式

public class Singleton {

private static Singleton instance;

private Singleton() {
}

public static Singleton getInstance() {
// 出现竞态条件
if (instance == null) { // 线程A
return new Singleton(); // 线程B
}

return instance;
}

}

懒汉模式会在使用到该实例的时候才会去实例化,但是这种实现方法会出现线程安全性问题,在高并发情况下,线程B还未初始化完成,线程A又来判断instance为null,也会再次实例化,就会出现多个实例。

3.懒汉式单例模式的线程安全写法

public class Singleton {

private static Singleton instance;

private Singleton() {
}

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

return instance;
}

}

我们可以在getInstance方法上加上synchronized关键字,虽然这样解决了多线程问题,但getInstance方法只能被串行执行,无论instance是否已经被初始化,这样代码的执行效率会大大降低。因此又有了双重检测锁机制。

4.双重检测锁实现单例模式

public class Singleton {

private static Singleton instance;

private Singleton() {
}

public static Singleton getInstance() {
if (instance == null) {
synchronized(Singleton.class) {

if (instance == null) {

                                        // 1.申请一块内存空间
      // 2.在这块空间里实例化对象
     // 3.instance的引用指向这块空间地址

instance = new Singleton(); // 线程A
}
}
}

return instance; // 线程B
}

}

首先检查instance是否为null,如果不为null就不用加锁,直接返回已有实例,减少了加锁的消耗。如果instance为null,再进行instance的实例化。这种实现方式看似没有问题,实际上也会出现问题,原因是对象的初始化过程不是原子的,大致会有代码上描述的三步,这三步有可能会被指令重排序,可能会以1,3,2的顺序执行,如果是这种执行顺序的话,线程B可能拿到未被初始化好的对象。

5.优化后的双重检测锁机制

public class Singleton {

private static volatile Singleton instance;

private Singleton() {
}

public static Singleton getInstance() {
if (instance == null) {
synchronized(Singleton.class) {

if (instance == null) {

                                        // 1.申请一块内存空间
      // 2.在这块空间里实例化对象
     // 3.instance的引用指向这块空间地址

instance = new Singleton(); // 线程
}
}
}

return instance; // 线程B
}

}

可以用volatile关键字修饰instance,阻止再实例化对象的时候进行指令重排序,这样就保证了线程B拿到的instance实例一定是单例且初始化好的。