欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

线程安全的单例模式创建的几种方式

程序员文章站 2022-07-14 09:24:57
...

一、线程不安全的懒汉模式,以及优为线程安全的懒汉模式

/**
 * 懒汉模式
 * 单例实例在第一次使用时进行创建
 */
public class SingletonExample1 {

    // 私有构造函数
    private SingletonExample1() {

    }

    // 单例对象
    private static SingletonExample1 instance = null;

    // 静态的工厂方法
    public static SingletonExample1 getInstance() {
        if (instance == null) {   //第一次线程A与线程B同时执行时会造成拿到的对象不同,创建了两次
            instance = new SingletonExample1();
        }
        return instance;
    }
}

说明:这种方式在单线程模式下是没有问题的,但是,在多线程的环境下,线程A与线程B同时执行到17行时,instance为空,则会创建出连个不同的对象返回,两个线程拿到的对象是不同的。所以不安全。

那怎么保证线程安全昵,我们可以用synchronized关键字来修饰该静态工厂方法,保证线程安全,同时只有一个线程可以执行该方法,代码如下:

public class SingletonExample3 {

    // 私有构造函数
    private SingletonExample3() {

    }

    // 单例对象
    private static SingletonExample3 instance = null;

    // 静态的工厂方法
    public static synchronized SingletonExample3 getInstance() { //synchronized 修饰
        if (instance == null) {
            instance = new SingletonExample3();
        }
        return instance;
    }
}

这样确实是实现了线程安全,但是我们不推荐用这种方式,因为在高并发下,同时只有一个线程可以访问该方法,效率低下。那有没有更好的方式昵,有,双重检测机制,两次判空,在判空里面再加上一个锁进行二次判空,保证多线程环境下创建对象的代码只有一个线程执行。代码如下:

public class SingletonExample4 {

    // 私有构造函数
    private SingletonExample4() {

    }

    // 单例对象
    private static SingletonExample4 instance = null;

    // 静态的工厂方法
    public static SingletonExample4 getInstance() {
        if (instance == null) { // 双重检测机制
            synchronized (SingletonExample4.class) { // 同步锁
                if (instance == null) {
                    instance = new SingletonExample4();
                }
            }
        }
        return instance;
    }
}

这个时候我们在看一下,线程A跟线程B会同时在14行堵住,同时只有一个线程执行,比如当线程A进去之后,线程B会在门口等待A执行完毕,当线程A执行完之后。线程B进去,这个时候instance已经不为空了,就会返回线程A创建的对象。这么来看的话,这个代码确实没问题,是线程安全的懒汉单例模式。

但实际上并不是这样,这个代码还是线程不安全的,这里就涉及到CPU的指令重排了。创建对象时,在CPU中分三步执行,

  1. memory = allocate()    分配对象的内存空间
  2. ctor Instance()             初始化对象
  3. instance = memory     设置instance指向刚分配的内存

那这里存在着什么问题昵,JVM和cpu优化,发生了指令重排,正常的执行顺序是这样没错,但如果在少数情况下,指令重排,就会造成正常的顺序发生变化,如下:

  1. memory = allocate()    分配对象的内存空间
  2. instance = memory     设置instance指向刚分配的内存
  3. ctor Instance()            初始化对象

这样的情况下,当线程A在创建对象时,线程B刚好走到13行,第一次判空,这个时候,线程A在CPU中的指令执行顺序按照重排后的执行。则在第二步的时候,对象还未初始化,就为instance指向了分配的内存。这时候,线程B刚好在13行判空,发现instance不为空,就直接返回了,但这个返回的对象并未初始化,依旧为空,造成问题的发生。

那这个问题如何避免昵,很简单,防止这个时候指令重排就行了,可以使用volatile关键字修饰instance变量,就可以避免指令重排从而使线程安全。完整代码如下:

/**
 * 懒汉模式 -》 双重同步锁单例模式
 * 单例实例在第一次使用时进行创建
 */
public class SingletonExample5 {

    // 私有构造函数
    private SingletonExample5() {

    }

    // 1、memory = allocate() 分配对象的内存空间
    // 2、ctorInstance() 初始化对象
    // 3、instance = memory 设置instance指向刚分配的内存

    // 单例对象 volatile + 双重检测机制 -> 禁止指令重排
    private volatile static SingletonExample5 instance = null;

    // 静态的工厂方法
    public static SingletonExample5 getInstance() {
        if (instance == null) { // 双重检测机制        // B
            synchronized (SingletonExample5.class) { // 同步锁
                if (instance == null) {
                    instance = new SingletonExample5(); // A - 3
                }
            }
        }
        return instance;
    }
}

二、单例的饿汉模式

这个较为简单就不做说明了,线程安全的饿汉模式。单例实例在类装载时进行创建

/**
 * 饿汉模式
 * 单例实例在类装载时进行创建
 */
public class SingletonExample2 {

    // 私有构造函数
    private SingletonExample2() {

    }

    // 单例对象
    private static SingletonExample2 instance = new SingletonExample2();

    // 静态的工厂方法
    public static SingletonExample2 getInstance() {
        return instance;
    }
}

或者用静态代码块的形式加载,需要注意的是,instance的声明要在静态代码块前面,代码如下:

/**
 * 饿汉模式
 * 单例实例在类装载时进行创建
 */
public class SingletonExample6 {

    // 私有构造函数
    private SingletonExample6() {

    }

    // 单例对象
    private static SingletonExample6 instance = null;

    static {
        instance = new SingletonExample6();
    }

    // 静态的工厂方法
    public static SingletonExample6 getInstance() {
        return instance;
    }
}

三、枚举的方式

推荐使用这种方式。

/**
 * 枚举模式:最安全
 */
public class SingletonExample7 {

    // 私有构造函数
    private SingletonExample7() {

    }

    public static SingletonExample7 getInstance() {
        return Singleton.INSTANCE.getInstance();
    }

    private enum Singleton {
        INSTANCE;

        private SingletonExample7 singleton;

        // JVM保证这个方法绝对只调用一次
        Singleton() {
            singleton = new SingletonExample7();
        }

        public SingletonExample7 getInstance() {
            return singleton;
        }
    }
}