设计模式之单例模式(线程安全)

单例模式

单例模式的应用场景就再也不叙述了,相信网上一找一大把。主要说一说单例模式的线程安全问题。单例模式主要分为两种实现:饿汉模式和懒汉模式。安全

饿汉模式

饿汉模式是指,项目一旦被启动,该类就会被加载完成,后续不在须要建立,整个项目的运行过程当中就只有一个实例。实现以下所示:多线程

public class Singleton {

    private Singleton () {
        System.out.println("我被建立了!");
    }

    /**
     * 饿汉模式,类加载时就会建立实例
     */
    private static Singleton instance = new Singleton();

    @PostConstruct
    public void init() {
        System.out.println("啥时候");
    }

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

该实例在类被加载时就已经建立,后续使用的都是同一个实例,不会再次建立,因此是线程安全的。也就是说单例模式的饿汉模式自然就不存在线程安全问题。并发

懒汉模式

懒汉模式与饿汉模式的最大区别就是,懒汉模式是在该类须要被使用的时候才会建立实例对象,而不是系统启动时加载类就建立。实现以下所示:单元测试

public class LazySingleton {

    private LazySingleton() {
        //看看是否会屡次建立
        System.out.println("我被建立了!");
    }

    /**
     * 懒汉模式,使用时才会建立实例
     */
    private static LazySingleton instance = null;

    //线程安全
    public static LazySingleton getInstance1() {
        try {
            //经过休眠,模拟并发状况
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        if (instance == null) {
            synchronized (LazySingleton.class) {
                if (instance == null) {
                    instance = new LazySingleton();
                    return instance;
                }
            }
        }
        return instance;
    }

    //线程不安全
    public static LazySingleton getInstance2() {
        try {
            //经过休眠,模拟并发状况
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        if (instance == null) {
            instance = new LazySingleton();
            return instance;
        }
        return instance;
    }
}

如代码中所示,getInstance2 就是正常状况下的懒汉模式实现,咱们经过休眠来模拟并发状况,在单元测试时,发现该类被实例化了2次,说明这种实如今单线程是没问题的,可是在多线程时就会发生线程安全问题。测试

@Test
public void singleTest() {
    for (int i =0; i<100; i++) {
        new Thread(LazySingleton::getInstance2).start();
        new Thread(LazySingleton::getInstance2).start();
        new Thread(LazySingleton::getInstance2).start();
        new Thread(LazySingleton::getInstance2).start();
        new Thread(LazySingleton::getInstance2).start();
        new Thread(LazySingleton::getInstance2).start();
        new Thread(LazySingleton::getInstance2).start();
        new Thread(LazySingleton::getInstance2).start();
        new Thread(LazySingleton::getInstance2).start();
        new Thread(LazySingleton::getInstance2).start();
    }
}

运行结果以下图所示:
image.png
此时,为确保懒汉模式状况下避免发生线程安全问题,咱们就须要使用到同步锁,如代码中getInstance1 所示,咱们为实例化的操做进行加锁,同时进行double check 操做,以免屡次实例化。固然避免线程安全问题的一种解决方法,还有其余方法,就不在此赘述。在加上同步锁以后,咱们再进行单元测试时,发现果真只建立了一次实例,完美地解决了线程安全问题。spa

@Test
public void singleTest() {
    for (int i =0; i<100; i++) {
        new Thread(LazySingleton::getInstance1).start();
        new Thread(LazySingleton::getInstance1).start();
        new Thread(LazySingleton::getInstance1).start();
        new Thread(LazySingleton::getInstance1).start();
        new Thread(LazySingleton::getInstance1).start();
        new Thread(LazySingleton::getInstance1).start();
        new Thread(LazySingleton::getInstance1).start();
        new Thread(LazySingleton::getInstance1).start();
        new Thread(LazySingleton::getInstance1).start();
        new Thread(LazySingleton::getInstance1).start();
    }
}

运行结果以下图所示:
image.png线程

总结

饿汉模式:由于在类加载是就已经建立了实例,后续不须要在建立实例,自然地避开了线程安全问题。
懒汉模式:由于是在使用时才进行实例化操做,因此存在着线程安全问题,此时须要经过加同步锁来确保多线程安全,避免屡次实例化。code

相关文章
相关标签/搜索