• 利用守护线程隐式关闭线程池


    在上期Java自定义异步功能实践文章中,我设计了一个关键字,传入一个闭包,然后异步执行闭包中的代码块。但是在实际工作中情况又更复杂了一些。因为在创建执行异步方法的线程池时候,遇到了一些问题。

    • 如何创建线程池core数值大于1,就必须手动关闭线程池
    • 如果创建线程池core=0,那么必须设置一个不为零的workQueue
    • 如果workQueue设置太小,无法容纳更多任务
    • 如果workQueue设置太大,无法新建更多线程(实际中只有1个线程被创建)

    经过一些人生的思考,我觉定使用守护进程来解决这个问题。参考创建Java守护线程

    思路

    执行异步方法的线程池,我使用定长线程池,设置线程数16,因为这个场景主要是在批量执行脚本使用,所以效率优先。设置workQueue为1百万(或者10万),目前使用中没有差别。

    如何在测试结束之后,利用守护进程的特性,等待main线程执行结束,然后回收资源。

    为了避免浪费,只在使用异步功能时再启用这个守护进程。

    分步实现

    创建线程池

    方法如下:

        private static volatile ExecutorService funPool;
        
        /**
         * 获取异步任务连接池
         * @return
         */
        static ExecutorService getFunPool() {
            if (funPool == null) {
                synchronized (ThreadPoolUtil.class) {
                    if (funPool == null) {
                        funPool = createFixedPool(Constant.POOL_SIZE);
                        daemon()
                    }
                }
            }
            return funPool
        }
    

    创建守护线程

        /**
         * 执行daemon线程,保障main方法结束后关闭线程池
         * @return
         */
        static boolean daemon() {
            def thread = new Thread(new Runnable() {
    
                @Override
                void run() {
                    while (checkMain()) {
                        SourceCode.sleep(1.0)
                    }
                    ThreadPoolUtil.shutFun()
                }
            })
            thread.setDaemon(true)
            thread.setName("FT-D")
            thread.start()
            logger.info("守护线程:{}开启!", thread.getName())
        }
    

    检查main线程是否存活

        /**
         * 检查main线程是否存活
         * @return
         */
        static boolean checkMain() {
            def count = Thread.activeCount()
            def group = Thread.currentThread().getThreadGroup()
            def threads = new Thread[count]
            group.enumerate(threads)
            for (i in 0..<count) {
                if (threads[i].getName() == "main")
                    return true
            }
            false
        }
    
    

    测试

    测试脚本

    简单使用了Groovy语法糖中times语法,含义就是从0~20遍历闭包内容,it表示遍历索引,从0开始到19。

        public static void main(String[] args) {
            20.times {
                def a = it as String
                fun{
                    sleep(1.0)
                    output(StringUtil.right("index:" + a, 10) + Time.getNow())
                }
            }
    
        }
    

    下面写个Java版本的比较容易理解:

        public static void main(String[] args) {
            for (int i = 0; i < 20; i++) {
                Integer a = i;
                fun(() -> {
                    sleep(1.0);
                    output(StringUtil.right("index:" + a, 10) + Time.getNow());
                    return null;
                });
            }
        }
    

    控制台输出

    INFO-> main 当前用户:oker,工作目录:/Users/oker/IdeaProjects/funtester/,系统编码格式:UTF-8,系统Mac OS X版本:10.16
    INFO-> main 守护线程:FT-D开启!
    INFO-> FT-13  index:12  20211011182658
    INFO-> FT-3   index:2   20211011182658
    INFO-> FT-16  index:15  20211011182658
    INFO-> FT-14  index:13  20211011182658
    INFO-> FT-5   index:4   20211011182658
    INFO-> FT-4   index:3   20211011182658
    INFO-> FT-12  index:11  20211011182658
    WARN-> FT-D 异步线程池关闭!
    INFO-> FT-10  index:9   20211011182658
    INFO-> FT-7   index:6   20211011182658
    INFO-> FT-2   index:1   20211011182658
    INFO-> FT-11  index:10  20211011182658
    INFO-> FT-8   index:7   20211011182658
    INFO-> FT-15  index:14  20211011182658
    INFO-> FT-1   index:0   20211011182658
    INFO-> FT-9   index:8   20211011182658
    INFO-> FT-6   index:5   20211011182658
    INFO-> FT-16  index:16  20211011182659
    INFO-> FT-7   index:19  20211011182659
    INFO-> FT-5   index:17  20211011182659
    INFO-> FT-12  index:18  20211011182659
    
    Process finished with exit code 0
    
    

    线程同步

    多线程同步依然使用java.util.concurrent.Phaser类,不过加上这个参数后有点破坏原来优雅的语法。

    Groovy版本:

        public static void main(String[] args) {
            def phaser = new Phaser(1)
            20.times {
                def a = it as String
                fun {
                    sleep(1.0)
                    output(StringUtil.right("index:" + a, 10) + Time.getNow())
                } , phaser
            }
            phaser.arriveAndAwaitAdvance()
        }
    

    这么写还是非常舒服的,不过编译器会报错,请忽略,编译器也不一定都是正确的。

    Java版本:

        public static void main(String[] args) {
            Phaser phaser = new Phaser(1);
            for (int i = 0; i < 20; i++) {
                Integer a = i;
                fun(() -> {
                    sleep(1.0);
                    output(StringUtil.right("index:" + a, 10) + Time.getNow());
                    return null;
                },phaser);
            }
            phaser.arriveAndAwaitAdvance();
        }
    

    控制台输出:

    INFO-> main 当前用户:oker,工作目录:/Users/oker/IdeaProjects/funtester/,系统编码格式:UTF-8,系统Mac OS X版本:10.16
    INFO-> main 守护线程:FT-D开启!
    INFO-> FT-11  index:10  20211011185814
    INFO-> FT-1   index:0   20211011185814
    INFO-> FT-5   index:4   20211011185814
    INFO-> FT-3   index:2   20211011185814
    INFO-> FT-16  index:15  20211011185814
    INFO-> FT-10  index:9   20211011185814
    INFO-> FT-7   index:6   20211011185814
    INFO-> FT-14  index:13  20211011185814
    INFO-> FT-9   index:8   20211011185814
    INFO-> FT-12  index:11  20211011185814
    INFO-> FT-15  index:14  20211011185814
    INFO-> FT-8   index:7   20211011185814
    INFO-> FT-6   index:5   20211011185814
    INFO-> FT-13  index:12  20211011185814
    INFO-> FT-2   index:1   20211011185814
    INFO-> FT-4   index:3   20211011185814
    INFO-> FT-3   index:16  20211011185815
    INFO-> FT-15  index:19  20211011185815
    INFO-> FT-7   index:18  20211011185815
    INFO-> FT-16  index:17  20211011185815
    WARN-> FT-D 异步线程池关闭!
    
    Process finished with exit code 0
    

    可以看到WARN-> FT-D 异步线程池关闭!是最后打印的,符合预期。

    Have Fun ~ Tester !

  • 相关阅读:
    Consuming RESTful Web服务
    任务调度
    查看公网出口ip
    Grafana变量
    正则表达式大杂烩
    seata 踩坑记录
    ES 重写分数查询
    浏览器下载文件乱码
    MySQL sql万花油优化
    ubuntu 使用杂记
  • 原文地址:https://www.cnblogs.com/FunTester/p/15469843.html
Copyright © 2020-2023  润新知