多线程下的单例模式(一)

我们都知道单例模式的实现最常见的两种实现,一种是恶汉模式,一个懒汉模式,也可以用枚举,静态代码块,静态内部类等实现方式。

1.恶汉模式:

(1)恶汉模式在类被加载的时候就创建好了;优点是多线程情况下百分百只会创建一个;缺点就是如果对象创建了很久不用就浪费空间,如果这个对象还有很多属性值,那就不推荐这个方法

(2)恶汉模式总共有三个步骤:

        ①私有的,静态的变量。类在加载的时候就创建

        ②私有构造方法,不允许其他人再来创建对象

        ③提供一个获取实例的方法

(3)先看恶汉模式代码:

/**
 * 恶汉模式,加上final不能被继承
 */
public final class HungrySingleton {
    //1.私有,静态的变量。类在加载的时候就创建
    private static HungrySingleton singleton = new HungrySingleton();
    //2.私有构造方法,不允许其他人再来创建对象
    private HungrySingleton(){}

    /**
     * 3.提供一个获取实例的方法
     * @return
     */
    public static HungrySingleton getInstance(){
        return singleton;
    }
}

(4)恶汉模式测试:

这里使用了计数器,计数器有阻塞代码代码运行的功能,直到计数归零后才会继续执行。

public class test2 {
    public static void main(String[] args)  {
        //初始化计数器
        int num = 100;
        //100个线程公共的计数器
        CountDownLatch countDownLatch = new CountDownLatch(num);
        //创建100个线程,但是并不是立即run,而是等100个线程创建完毕,此时计数器归零,
        //100个线程同时run
        for (int i = 0; i < num; i++) {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        //阻塞后续代码,直到countDownLatch.countDown()执行了一百次
                        countDownLatch.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //打印恶汉模式的单例对象
                    System.out.println(HungrySingleton.getInstance());
                }
            });
            thread.start();
            //计数器减1
            countDownLatch.countDown();
        }
    }
}

(5)测试结果

可以发现所有的实例都是一样的,所以多线程下也不存在安全问题。

 2.懒汉模式

懒汉模式是在用到这个实例的时候才创建单利对象,不会浪费空间,多线程下编码如果编码不合理会创建多个对象。

(1)懒汉式的同步方法实现。优点:编码简单。缺点:synchronized影响效率.

/**
 * 懒汉模式+同步方法
 */
public class LazySingleton {

    //私有
    private static LazySingleton singleton = null;
    //私有构造方法,不允许其他人再来创建对象
    private LazySingleton(){}

    /**
     * 提供一个获取实例的方法
     * @return
     */
    public static synchronized LazySingleton getInstance(){
        if(singleton == null ){
            singleton = new LazySingleton();
        }
        return singleton;
    }
}

(2)懒汉式的双重校验锁(DCL)

/**
 * 懒汉模式+DCL
 */
public class LazySingleton {

    //私有
    private static LazySingleton singleton = null;
    //私有构造方法,不允许其他人再来创建对象
    private LazySingleton(){}

    /**
     * 提供一个获取实例的方法
     * @return
     */
    public static  LazySingleton getInstance(){
        if(singleton == null){ //第一次校验是为了性能
            synchronized (LazySingleton.class){
                if(singleton == null){ //第二次校验是为了安全
                    singleton = new LazySingleton();
                }
            }
        }
        return singleton;
    }
}

其实这种方法也会存在问题,看这段代码:

singleton = new LazySingleton();

这段代码可以分为三个步骤:

①分配对象内存空

②初始化对象

③设置singleton 指向刚刚分配的内存地址

        JVM在执行的时候可能会发生指令重排,也就是实际上的顺序可能会出现①③②。当线程1执行完①③步骤时,线程时间片没有了,CPU有上下文的切换,此时来了一个线程2也想拿对象,但是拿到了一个未初始化的对象,用这个对象去调用LazySingleton类中的方法就会出现空指针。

        当然这种概率是实在是小,之前有测试过JVM指令重排的概率大概有万分之一,有时候是几十万分之一,外加线程刚好执行完①③就没有时间片的概率也很小。那么出现空指针的情况概率就更小了。

       但是为了绝对的安全可以这样更改:

        private volatile static LazySingleton singleton = null;

volatile 关键字的作用就是禁止JVM指令重排,就不会初选上述的情况。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值