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

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

/**
 * 懒汉模式
 * 单例实例在第一次使用时进行创建
 */
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;
        }
    }
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值