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

FunTester · 2021年10月27日 · 893 次阅读

在上期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 !

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
暂无回复。
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册