手写高并发下线程安全的单例模式

摘要:主要介绍使用枚举类实现JAVA单例模式,以及在高并发环境下验证此单例模式是线程安全的。

§前言

  单例模式的作用:确保一个类只有一个实例,自行实例化并向系统提供这个实例。
  单例模式的三个主要特点:

1.  构造方法私有化;
2.  私有静态引用指向自己实例;
3.  以自己实例为返回值的公有静态方法。

  更多关于单例模式的介绍,请参考《Java与设计模式之单例模式(上)六种实现方式》和《Java与设计模式之单例模式(下) 安全的单例模式》。

§案例分析

  定义一个业务类,假设这个业务类在高并发环境被应用的时候,需要保证线程安全。

/**
 * 业务类
 *
 * @author Wiener
 * @date 2021/3/7
 */
public class DealBusiness {
    public DealBusiness(){
        System.out.println("被单例模式处理后的业务类");
    }

    /**
     * 业务逻辑
     * @param code
     */
    public void otherMethod(String code) {
        System.out.println("Do sth." + code);
    }
}

  使用枚举类的构造方法在类加载的时候,初始化业务类,以达到线程安全的目的:

/**
 *  枚举类单例
 *
 * @author Wiener
 * @date 2021/3/7
 */
public enum EnumSingleton {
    INSTANCE;

    private DealBusiness instance;
    public DealBusiness getInstance() {
        return instance;
    }

    private EnumSingleton () {
        System.out.println("===执行初始化,只被执行一次===");
        instance = new DealBusiness();
    }
}

  创建测试用例:

public class SingletonMultiThread {

    private static String BUSINESS_CODE = " Wiener";

    /**
     * 如果单例的哈希值一样,则说明是线程安全的单例
     *
     * @param args
     * @throws InterruptedException
     */
    public static void main(String[] args) throws InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(50);
        for (int i = 0; i < 10; i++) {
            final int ii = i;
            executorService.execute(() -> {
                EnumSingleton ton = EnumSingleton.INSTANCE;
                System.out.println(ton.getInstance().hashCode());
                ton.getInstnce().otherMethod(BUSINESS_CODE + ii);
//                System.out.println(EnumSingletonPlus.getInstance().hashCode());  // ①
            });
        }
        System.out.println("--------*执行完所有已有任务后,关闭线程池*---------");
        executorService.shutdown();
    }
}

  执行结果:

===执行初始化,只被执行一次===
被单例模式处理后的业务类
1651822822
1651822822
1651822822
1651822822
1651822822
1651822822
--------*执行完所有已有任务后,关闭线程池*---------
1651822822
1651822822
1651822822
1651822822
Do sth. Wiener1
Do sth. Wiener7
Do sth. Wiener3
Do sth. Wiener5
Do sth. Wiener4
Do sth. Wiener6
Do sth. Wiener2
Do sth. Wiener8
Do sth. Wiener0
Do sth. Wiener9

  从执行结果上来看,通过单例获取的每个实例ton的哈希值都相等,说明EnumSingleton实际只初始化了一次,和初始化时打印日志的次数是吻合的。由此可见,枚举类单例模式保证了多线程高并发下的线程安全性。大家在增加如下改进版的枚举类单例模式类后,可以放开①处的注释,验证其也是线程安全的。


/**
 * 升级版——在类里创建枚举类
 *
 * @author Wiener
 * @date 2021/3/7
 */
public class EnumSingletonPlus {
    //私有化构造函数
    private EnumSingletonPlus() {
    }

    private static enum EnumSingleton {
        INSTANCE;

        private EnumSingletonPlus demo;

        //私有化枚举的构造函数
        private EnumSingleton() {
            System.out.println("working");
            demo = new EnumSingletonPlus();
        }

        public EnumSingletonPlus getInstance() {
            return demo;
        }
    }

    public void otherMethod(String code) {
        System.out.println("Do sth." + code);
    }
    //对外暴露一个获取对象的静态方法
    public static EnumSingletonPlus getInstance() {
        return EnumSingleton.INSTANCE.getInstance();
    }
}

§小结

临近尾声,综述一下单例模式的优缺点。

优点

(1)单例模式由于在内存中只有一个实例,减少内存开支,特别是一个对象需要频繁地创建销毁时,而且创建或销毁时性能又无法优化,它的优势就彰显出来了。
(2)单例模式由于活动的单例只有一个,所以,减少系统的性能开销,当一个对象产生需要比较多的资源时,如读取配置,产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决。
(3)避免对资源的多重占用,例如一个写文件操作,由于只有一个实例存在内存中,避免对同一个资源文件的同时写操作。
(4) 单例模式可以在系统设置全局的访问点,优化和共享资源访问,例如,可以设计一个单例类,负责所有数据表的映射处理。

缺点

(1) 单例模式没有抽象层,扩展很困难,若要扩展,除了修改代码基本上没有第二种途径可以实现。
(2) 单例类的职责过重,在一定程度上违背了“单一职责原则”。
(3) 滥用单例将带来一些负面问题,如:为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;如果实例化的对象长时间不被利用,系统会认为是垃圾而被回收,这将导致对象状态的丢失。

§Reference

posted @ 2021-03-07 15:26  楼兰胡杨  阅读(428)  评论(0编辑  收藏  举报