Bootstrap

cosycode类分享:可控制的单循环线程 com.github.cosycode.common.thread.CtrlLoopThreadComp

cosycode类分享:可控制的单循环线程

CtrlLoopThreadComp 是我在处理异步信息的时候封装的一个线程类, 简单来说就是对一个方法进行循环调用.

普通线程里面加 while 循环

我在开发的时候遇到一些在线程里面加while循环的例子, 如下

@Test
public void baseWhileTest() {
   Runnable runnable =  () -> {
      while (!Thread.currentThread().isInterrupted()) {
            // 执行相关逻辑
            log.info("执行相关逻辑");
            try {
               // 防止 CPU 被打满
               Thread.sleep(500);
            } catch (InterruptedException e) {
               Thread.currentThread().interrupt();
               log.info("线程从睡眠中唤醒");
            }
      }
      log.info("线程结束");
   };

   // 启动线程
   final Thread thread = new Thread(runnable, "while 循环线程");
   thread.start();

   // 一段时间后(5000毫秒后)暂停
   Throws.con(5000, Thread::sleep).logThrowable();
   thread.interrupt();
}

运行结果

[INFO] 2022-02-20 22:02:20.260 [while 循环线程] (CtrlLoopThreadCompTest.java:168) 执行相关逻辑
[INFO] 2022-02-20 22:02:20.769 [while 循环线程] (CtrlLoopThreadCompTest.java:168) 执行相关逻辑
[INFO] 2022-02-20 22:02:21.284 [while 循环线程] (CtrlLoopThreadCompTest.java:168) 执行相关逻辑
[INFO] 2022-02-20 22:02:21.803 [while 循环线程] (CtrlLoopThreadCompTest.java:168) 执行相关逻辑
[INFO] 2022-02-20 22:02:22.314 [while 循环线程] (CtrlLoopThreadCompTest.java:168) 执行相关逻辑
[INFO] 2022-02-20 22:02:22.828 [while 循环线程] (CtrlLoopThreadCompTest.java:168) 执行相关逻辑
[INFO] 2022-02-20 22:02:23.330 [while 循环线程] (CtrlLoopThreadCompTest.java:168) 执行相关逻辑
[INFO] 2022-02-20 22:02:23.837 [while 循环线程] (CtrlLoopThreadCompTest.java:168) 执行相关逻辑
[INFO] 2022-02-20 22:02:24.338 [while 循环线程] (CtrlLoopThreadCompTest.java:168) 执行相关逻辑
[INFO] 2022-02-20 22:02:24.839 [while 循环线程] (CtrlLoopThreadCompTest.java:168) 执行相关逻辑
[INFO] 2022-02-20 22:02:25.267 [while 循环线程] (CtrlLoopThreadCompTest.java:174) 线程从睡眠中唤醒
[INFO] 2022-02-20 22:02:25.267 [while 循环线程] (CtrlLoopThreadCompTest.java:177) 线程结束

上面是一个相当简单的例子,但事实上遇到的并没有这么简单,尤其是业余进行swing开发的时候,循环对消息事件进行处理的时候,就需要这个线程一会正常循环运行,一会暂停处理相关消息。还有的需要执行几次循环后暂停等等各种操作。

于是便封装了一个 CtrlLoopThreadComp 类,专门处理这种使用一个异步线程,额外去循环调用方法的操作。

CtrlLoopThreadComp 的循环调用代码

如下,就是使用 CtrlLoopThreadComp 类对上面的线程里面加while循环的代码的转译.

@Test
public void CoBaseTest1() {
   final CtrlLoopThreadComp threadComp = CtrlLoopThreadComp.ofRunnable(() -> {
         log.info("执行相关逻辑");
      })
      // 执行出现异常则继续下一次执行
      .catchFun(CtrlLoopThreadComp.CATCH_FUNCTION_CONTINUE)
      // 每 500 毫秒执行一次
      .setMillisecond(500)
      // 线程名称名为 COM-RUN
      .setName("可循环控制线程");

   // 启动线程
   log.info(" ======> 启动线程");
   threadComp.start();

   // 一段时间后(5000毫秒后)暂停
   Throws.con(5000, Thread::sleep).logThrowable();
   thread.interrupt();
}

运行结果

[INFO] 2022-02-20 21:59:56.414 [main] (CtrlLoopThreadCompTest.java:204)  ======> 启动线程
[DEBUG] 2022-02-20 21:59:56.414 [可循环控制线程] (CtrlLoopThreadComp.java:425) CtrlLoopThread [可循环控制线程] start!!!
[INFO] 2022-02-20 21:59:56.414 [可循环控制线程] (CtrlLoopThreadCompTest.java:194) 执行相关逻辑
[INFO] 2022-02-20 21:59:56.928 [可循环控制线程] (CtrlLoopThreadCompTest.java:194) 执行相关逻辑
[INFO] 2022-02-20 21:59:57.443 [可循环控制线程] (CtrlLoopThreadCompTest.java:194) 执行相关逻辑
[INFO] 2022-02-20 21:59:57.944 [可循环控制线程] (CtrlLoopThreadCompTest.java:194) 执行相关逻辑
[INFO] 2022-02-20 21:59:58.450 [可循环控制线程] (CtrlLoopThreadCompTest.java:194) 执行相关逻辑
[INFO] 2022-02-20 21:59:58.960 [可循环控制线程] (CtrlLoopThreadCompTest.java:194) 执行相关逻辑
[INFO] 2022-02-20 21:59:59.466 [可循环控制线程] (CtrlLoopThreadCompTest.java:194) 执行相关逻辑
[INFO] 2022-02-20 21:59:59.976 [可循环控制线程] (CtrlLoopThreadCompTest.java:194) 执行相关逻辑
[INFO] 2022-02-20 22:00:00.490 [可循环控制线程] (CtrlLoopThreadCompTest.java:194) 执行相关逻辑
[INFO] 2022-02-20 22:00:00.991 [可循环控制线程] (CtrlLoopThreadCompTest.java:194) 执行相关逻辑
[DEBUG] 2022-02-20 22:00:01.424 [可循环控制线程] (CtrlLoopThreadComp.java:501) CtrlLoopThread [可循环控制线程] was interrupted during sleep
[DEBUG] 2022-02-20 22:00:01.424 [可循环控制线程] (CtrlLoopThreadComp.java:506) CtrlLoopThread [可循环控制线程] end!!!

当然它的功能也不可能这么简单, 它的一个好用的地方就是下面的控制的过程

CtrlLoopThreadComp 线程控制方法

方法描述
pause()线程在调用完当前 loop 循环后, 暂停
pause(long)线程在调用完当前 loop 循环后, 暂停long毫秒后自动恢复, 其中使用的是 wait(long)
pauseAfterLoopTime(int)线程接下来执行int次 loop 循环后, 暂停.
wake()线程在暂停的状态下恢复 loop 循环调用
startOrWake()线程启动或恢复, 若是内置线程没有启动的情况下启动内置线程, 若是暂停状态下则对线程进行唤醒
close()线程结束当前 loop 循环之后, 线程关闭
start()内置线程启动, 直接调用的thread.start() 方法, 二次调用会抛出异常.
startIfNotStart()如果内置线程没有启动的时候启动, 可以多次调用, 但是只有第一次生效.

线程控制方法测试

/**
* 基础功能测试
*/
@Test
public void baseTest() {
   final CtrlLoopThreadComp threadComp = CtrlLoopThreadComp.ofRunnable(() -> {
               final String s = UUID.randomUUID().toString();
               log.info("-----------------------开始执行线程方法 : {}", s);
               // 睡眠 200 毫秒, 睡眠期间有异常则直接转换为 RuntimeException 抛出
               Throws.con(200, Thread::sleep).runtimeExp();
               log.info("-----------------------结束执行线程方法 : {}", s);
            })
            // 执行出现异常则继续下一次执行
            .catchFun(CtrlLoopThreadComp.CATCH_FUNCTION_CONTINUE)
            // 每 100 毫秒执行一次
            .setMillisecond(300)
            // 线程名称名为 COM-RUN
            .setName("可循环控制线程 1");
   // 启动线程
   log.info(" ======> 启动循环线程");
   threadComp.start();
   Throws.con(2000, Thread::sleep).runtimeExp();

   log.info(" ======> 暂停循环线程");
   threadComp.pause();
   Throws.con(2000, Thread::sleep).runtimeExp();

   log.info(" ======> 启动或唤醒循环线程, 1秒后再次暂停");
   threadComp.startOrWake();
   threadComp.pause(1000);
   Throws.con(2000, Thread::sleep).runtimeExp();

   log.info(" ======> 启动循环线程, 再执行 5 次循环后自动暂停");
   threadComp.pauseAfterLoopTime(5);
   Throws.con(5000, Thread::sleep).runtimeExp();

   log.info(" ======> 启动或唤醒循环线程, 2秒后 线程关闭");
   threadComp.startOrWake();
   Throws.con(2000, Thread::sleep).runtimeExp();
   threadComp.close();
}

运行结果

[INFO] 2022-02-20 22:09:39.257 [main] (CtrlLoopThreadCompTest.java:45)  ======> 启动循环线程
[DEBUG] 2022-02-20 22:09:39.267 [可循环控制线程 1] (CtrlLoopThreadComp.java:425) CtrlLoopThread [可循环控制线程 1] start!!!
[INFO] 2022-02-20 22:09:39.366 [可循环控制线程 1] (CtrlLoopThreadCompTest.java:33) -----------------------开始执行线程方法 : 17947299-b4e2-4c76-a25d-bdab500cde0a
[INFO] 2022-02-20 22:09:39.582 [可循环控制线程 1] (CtrlLoopThreadCompTest.java:36) -----------------------结束执行线程方法 : 17947299-b4e2-4c76-a25d-bdab500cde0a
[INFO] 2022-02-20 22:09:39.883 [可循环控制线程 1] (CtrlLoopThreadCompTest.java:33) -----------------------开始执行线程方法 : b1f9853e-d560-4430-8a1f-b2ab22f2891c
[INFO] 2022-02-20 22:09:40.098 [可循环控制线程 1] (CtrlLoopThreadCompTest.java:36) -----------------------结束执行线程方法 : b1f9853e-d560-4430-8a1f-b2ab22f2891c
[INFO] 2022-02-20 22:09:40.403 [可循环控制线程 1] (CtrlLoopThreadCompTest.java:33) -----------------------开始执行线程方法 : ca4b0fb6-863e-45e2-90e5-71960bdd24cf
[INFO] 2022-02-20 22:09:40.619 [可循环控制线程 1] (CtrlLoopThreadCompTest.java:36) -----------------------结束执行线程方法 : ca4b0fb6-863e-45e2-90e5-71960bdd24cf
[INFO] 2022-02-20 22:09:40.920 [可循环控制线程 1] (CtrlLoopThreadCompTest.java:33) -----------------------开始执行线程方法 : 3778424d-5ad2-44c3-b93b-7fda3657b964
[INFO] 2022-02-20 22:09:41.136 [可循环控制线程 1] (CtrlLoopThreadCompTest.java:36) -----------------------结束执行线程方法 : 3778424d-5ad2-44c3-b93b-7fda3657b964
[INFO] 2022-02-20 22:09:41.278 [main] (CtrlLoopThreadCompTest.java:49)  ======> 暂停循环线程
[DEBUG] 2022-02-20 22:09:41.441 [可循环控制线程 1] (CtrlLoopThreadComp.java:448) CtrlLoopThread [可循环控制线程 1] pause!!!
[INFO] 2022-02-20 22:09:43.293 [main] (CtrlLoopThreadCompTest.java:53)  ======> 启动或唤醒循环线程, 1秒后再次暂停
[DEBUG] 2022-02-20 22:09:43.293 [可循环控制线程 1] (CtrlLoopThreadComp.java:466) CtrlLoopThread [可循环控制线程 1] wake!!!
[DEBUG] 2022-02-20 22:09:43.293 [可循环控制线程 1] (CtrlLoopThreadComp.java:448) CtrlLoopThread [可循环控制线程 1] pause!!!
[DEBUG] 2022-02-20 22:09:44.308 [可循环控制线程 1] (CtrlLoopThreadComp.java:466) CtrlLoopThread [可循环控制线程 1] wake!!!
[INFO] 2022-02-20 22:09:44.308 [可循环控制线程 1] (CtrlLoopThreadCompTest.java:33) -----------------------开始执行线程方法 : ef85de83-f102-44b1-bc99-01b7a30962c6
[INFO] 2022-02-20 22:09:44.511 [可循环控制线程 1] (CtrlLoopThreadCompTest.java:36) -----------------------结束执行线程方法 : ef85de83-f102-44b1-bc99-01b7a30962c6
[INFO] 2022-02-20 22:09:44.828 [可循环控制线程 1] (CtrlLoopThreadCompTest.java:33) -----------------------开始执行线程方法 : f8b60de6-d2ab-43b5-9510-4a22bd1646c4
[INFO] 2022-02-20 22:09:45.028 [可循环控制线程 1] (CtrlLoopThreadCompTest.java:36) -----------------------结束执行线程方法 : f8b60de6-d2ab-43b5-9510-4a22bd1646c4
[INFO] 2022-02-20 22:09:45.307 [main] (CtrlLoopThreadCompTest.java:58)  ======> 启动循环线程, 再执行 5 次循环后自动暂停
[INFO] 2022-02-20 22:09:45.348 [可循环控制线程 1] (CtrlLoopThreadCompTest.java:33) -----------------------开始执行线程方法 : 7c55f307-1725-417a-a882-e2fd7f9ff6c2
[INFO] 2022-02-20 22:09:45.551 [可循环控制线程 1] (CtrlLoopThreadCompTest.java:36) -----------------------结束执行线程方法 : 7c55f307-1725-417a-a882-e2fd7f9ff6c2
[INFO] 2022-02-20 22:09:45.852 [可循环控制线程 1] (CtrlLoopThreadCompTest.java:33) -----------------------开始执行线程方法 : d1267012-930e-4d62-aac1-656ec014cbb7
[INFO] 2022-02-20 22:09:46.052 [可循环控制线程 1] (CtrlLoopThreadCompTest.java:36) -----------------------结束执行线程方法 : d1267012-930e-4d62-aac1-656ec014cbb7
[INFO] 2022-02-20 22:09:46.356 [可循环控制线程 1] (CtrlLoopThreadCompTest.java:33) -----------------------开始执行线程方法 : fdce9373-cb38-4fc7-8aef-9aa513147411
[INFO] 2022-02-20 22:09:46.563 [可循环控制线程 1] (CtrlLoopThreadCompTest.java:36) -----------------------结束执行线程方法 : fdce9373-cb38-4fc7-8aef-9aa513147411
[INFO] 2022-02-20 22:09:46.864 [可循环控制线程 1] (CtrlLoopThreadCompTest.java:33) -----------------------开始执行线程方法 : 7f13ee60-8415-4a40-ad88-217145561956
[INFO] 2022-02-20 22:09:47.064 [可循环控制线程 1] (CtrlLoopThreadCompTest.java:36) -----------------------结束执行线程方法 : 7f13ee60-8415-4a40-ad88-217145561956
[INFO] 2022-02-20 22:09:47.384 [可循环控制线程 1] (CtrlLoopThreadCompTest.java:33) -----------------------开始执行线程方法 : 24033216-67bd-43b7-ad96-7d602f90b87e
[INFO] 2022-02-20 22:09:47.591 [可循环控制线程 1] (CtrlLoopThreadCompTest.java:36) -----------------------结束执行线程方法 : 24033216-67bd-43b7-ad96-7d602f90b87e
[DEBUG] 2022-02-20 22:09:47.892 [可循环控制线程 1] (CtrlLoopThreadComp.java:448) CtrlLoopThread [可循环控制线程 1] pause!!!
[INFO] 2022-02-20 22:09:50.321 [main] (CtrlLoopThreadCompTest.java:62)  ======> 启动或唤醒循环线程, 2秒后 线程关闭
[DEBUG] 2022-02-20 22:09:50.321 [可循环控制线程 1] (CtrlLoopThreadComp.java:466) CtrlLoopThread [可循环控制线程 1] wake!!!
[INFO] 2022-02-20 22:09:50.321 [可循环控制线程 1] (CtrlLoopThreadCompTest.java:33) -----------------------开始执行线程方法 : f70f0969-a240-4208-abac-2a0dfd1fed8d
[INFO] 2022-02-20 22:09:50.521 [可循环控制线程 1] (CtrlLoopThreadCompTest.java:36) -----------------------结束执行线程方法 : f70f0969-a240-4208-abac-2a0dfd1fed8d
[INFO] 2022-02-20 22:09:50.822 [可循环控制线程 1] (CtrlLoopThreadCompTest.java:33) -----------------------开始执行线程方法 : f4908c34-f773-46f4-b56b-d451bb0afe3a
[INFO] 2022-02-20 22:09:51.023 [可循环控制线程 1] (CtrlLoopThreadCompTest.java:36) -----------------------结束执行线程方法 : f4908c34-f773-46f4-b56b-d451bb0afe3a
[INFO] 2022-02-20 22:09:51.341 [可循环控制线程 1] (CtrlLoopThreadCompTest.java:33) -----------------------开始执行线程方法 : bda3e0fc-f40b-4241-bc5d-6c578011956b
[INFO] 2022-02-20 22:09:51.548 [可循环控制线程 1] (CtrlLoopThreadCompTest.java:36) -----------------------结束执行线程方法 : bda3e0fc-f40b-4241-bc5d-6c578011956b
[INFO] 2022-02-20 22:09:51.849 [可循环控制线程 1] (CtrlLoopThreadCompTest.java:33) -----------------------开始执行线程方法 : 02d70f5c-be3e-446d-b61f-9bce3313275f
[INFO] 2022-02-20 22:09:52.049 [可循环控制线程 1] (CtrlLoopThreadCompTest.java:36) -----------------------结束执行线程方法 : 02d70f5c-be3e-446d-b61f-9bce3313275f
[DEBUG] 2022-02-20 22:09:52.326 [可循环控制线程 1] (CtrlLoopThreadComp.java:501) CtrlLoopThread [可循环控制线程 1] was interrupted during sleep
[DEBUG] 2022-02-20 22:09:52.326 [可循环控制线程 1] (CtrlLoopThreadComp.java:506) CtrlLoopThread [可循环控制线程 1] end!!!

CtrlLoopThreadComp 实现异步消息队列处理1

简单的异步处理队列

// 异步消息队列
private final BlockingQueue<String> blockingQueue = new LinkedBlockingQueue<>(1000);

/**
* 异步消息处理测试
*/
@Test
public void asynchronousQueueTest() {
   // 异步处理器
   final CtrlLoopThreadComp threadComp = CtrlLoopThreadComp.ofRunnable(() -> {
               try {
                  String str = blockingQueue.take();
                  log.info("异步线程打印信息 : {}", str);
               } catch (InterruptedException e) {
                  e.printStackTrace();
               }
            })
            // 执行出现异常则继续下一次执行
            .catchFun(CtrlLoopThreadComp.CATCH_FUNCTION_CONTINUE)
            .setMillisecond(0)
            .setName("异步处理线程");
   threadComp.start();

   // 即时往队列里面添加消息, 异步处理线程即时处理
   blockingQueue.add("消息1");
   blockingQueue.add("消息2");
   blockingQueue.add("消息3");
   blockingQueue.add("消息4");
   blockingQueue.add("消息5");

   // 主线程睡眠 10 毫秒, 方便队列消息能够处理完成
   Throws.con(10, Thread::sleep).logThrowable();
}

打印日志

[INFO] 2022-02-20 22:17:55.424 [异步处理线程] (CtrlLoopThreadCompTest.java:189) 异步线程打印信息 : 消息1
[INFO] 2022-02-20 22:17:55.424 [异步处理线程] (CtrlLoopThreadCompTest.java:189) 异步线程打印信息 : 消息2
[INFO] 2022-02-20 22:17:55.424 [异步处理线程] (CtrlLoopThreadCompTest.java:189) 异步线程打印信息 : 消息3
[INFO] 2022-02-20 22:17:55.424 [异步处理线程] (CtrlLoopThreadCompTest.java:189) 异步线程打印信息 : 消息4
[INFO] 2022-02-20 22:17:55.424 [异步处理线程] (CtrlLoopThreadCompTest.java:189) 异步线程打印信息 : 消息5

CtrlLoopThreadComp 实现异步消息队列处理2

实现先异步处理6条消息, 之后暂停, 再唤醒

// 异步消息队列
private final BlockingQueue<String> blockingQueue = new LinkedBlockingQueue<>(1000);

/**
* 异步消息处理测试2
*/
@Test
public void asynchronousQueueTest2() {
   // 异步处理器
   final CtrlLoopThreadComp threadComp = CtrlLoopThreadComp.ofRunnable(() -> {
               try {
                  String str = blockingQueue.take();
                  log.info("异步线程打印信息 : {}", str);
               } catch (InterruptedException e) {
                  e.printStackTrace();
               }
            })
            // 执行出现异常则继续下一次执行
            .catchFun(CtrlLoopThreadComp.CATCH_FUNCTION_CONTINUE)
            .setMillisecond(0)
            .setName("异步处理线程");

   // 处理 6 条消息后暂停
   threadComp.pauseAfterLoopTime(6);
   threadComp.start();

   // 即时往队列里面添加消息, 异步处理线程即时处理
   blockingQueue.add("消息1");
   blockingQueue.add("消息2");
   blockingQueue.add("消息3");
   blockingQueue.add("消息4");
   blockingQueue.add("消息5");
   blockingQueue.add("消息6");
   blockingQueue.add("消息7");
   blockingQueue.add("消息8");
   blockingQueue.add("消息9");
   blockingQueue.add("消息10");

   Throws.con(2000, Thread::sleep).logThrowable();
   log.info("发现继续添加消息,不再进行处理");

   log.info("启动线程后继续处理消息");
   threadComp.wake();

   Throws.con(10, Thread::sleep).logThrowable();
}

打印日志

[INFO] 2022-02-20 22:21:45.235 [异步处理线程] (CtrlLoopThreadCompTest.java:221) 异步线程打印信息 : 消息1
[INFO] 2022-02-20 22:21:45.235 [异步处理线程] (CtrlLoopThreadCompTest.java:221) 异步线程打印信息 : 消息2
[INFO] 2022-02-20 22:21:45.235 [异步处理线程] (CtrlLoopThreadCompTest.java:221) 异步线程打印信息 : 消息3
[INFO] 2022-02-20 22:21:45.235 [异步处理线程] (CtrlLoopThreadCompTest.java:221) 异步线程打印信息 : 消息4
[INFO] 2022-02-20 22:21:45.235 [异步处理线程] (CtrlLoopThreadCompTest.java:221) 异步线程打印信息 : 消息5
[INFO] 2022-02-20 22:21:45.235 [异步处理线程] (CtrlLoopThreadCompTest.java:221) 异步线程打印信息 : 消息6
[DEBUG] 2022-02-20 22:21:45.235 [异步处理线程] (CtrlLoopThreadComp.java:448) CtrlLoopThread [异步处理线程] pause!!!
[INFO] 2022-02-20 22:21:47.239 [main] (CtrlLoopThreadCompTest.java:248) 发现继续添加消息,不再进行处理
[INFO] 2022-02-20 22:21:47.239 [main] (CtrlLoopThreadCompTest.java:250) 启动线程后继续处理消息
[DEBUG] 2022-02-20 22:21:47.239 [异步处理线程] (CtrlLoopThreadComp.java:466) CtrlLoopThread [异步处理线程] wake!!!
[INFO] 2022-02-20 22:21:47.239 [异步处理线程] (CtrlLoopThreadCompTest.java:221) 异步线程打印信息 : 消息7
[INFO] 2022-02-20 22:21:47.239 [异步处理线程] (CtrlLoopThreadCompTest.java:221) 异步线程打印信息 : 消息8
[INFO] 2022-02-20 22:21:47.239 [异步处理线程] (CtrlLoopThreadCompTest.java:221) 异步线程打印信息 : 消息9
[INFO] 2022-02-20 22:21:47.239 [异步处理线程] (CtrlLoopThreadCompTest.java:221) 异步线程打印信息 : 消息10

并发测试

pause(), startOrWake(), .wake()等方法都是可以在多线程之中重复调用的.

并发调用 pause(), startOrWake(), .wake(), 看下执行逻辑是否出错

代码如下

/**
* 并发调用几个方法, 看下执行逻辑是否出错
*/
@Test
public void ConcurrentTest() throws InterruptedException {
   final CtrlLoopThreadComp threadComp = CtrlLoopThreadComp.ofRunnable(() -> {
               final String s = UUID.randomUUID().toString();
               log.info("-----------------------开始执行线程方法 : {}", s);
               // 睡眠 200 毫秒, 睡眠期间有异常则直接转换为 RuntimeException 抛出
               Throws.con(RandomUtils.nextInt(0, 3), Thread::sleep).runtimeExp();
               log.info("-----------------------结束执行线程方法 : {}", s);
            })
            // 执行出现异常则继续下一次执行
            .catchFun(CtrlLoopThreadComp.CATCH_FUNCTION_CONTINUE)
            // 每 100 毫秒执行一次
            .setMillisecond(100)
            // 线程名称名为 COM-RUN
            .setName("可循环控制线程 2");
   // 启动线程
   log.info(" ======> 启动线程");
   threadComp.start();

   final int loop = 100000;
   CountDownLatch countDownLatch = new CountDownLatch(loop);
   IntStream.range(0, loop).parallel().forEach(num -> {
      final int i = RandomUtils.nextInt(0, 5);
      switch (i) {
            case 0:
               threadComp.pause();
               break;
            case 1:
               threadComp.startOrWake();
               break;
            case 2:
               threadComp.pause(1);
               break;
            case 3:
               threadComp.pause();
               Throws.con(1, Thread::sleep).runtimeExp();
               threadComp.wake();
               break;
            case 4:
               threadComp.startIfNotStart();
               break;
            default:
      }
      countDownLatch.countDown();
   });

   countDownLatch.await();
   log.info(" ======> 执行完毕关闭线程");
   threadComp.close();
   threadComp.close();
}

执行日志如下

  • 100000 次并发, 执行逻辑并没有出错
  • 注意: 由于日志较多, 删除了一些日志.
[INFO] 2022-02-20 22:26:35.150 [main] (CtrlLoopThreadCompTest.java:87)  ======> 启动线程
[DEBUG] 2022-02-20 22:26:35.150 [可循环控制线程 2] (CtrlLoopThreadComp.java:425) CtrlLoopThread [可循环控制线程 2] start!!!
[INFO] 2022-02-20 22:26:35.273 [可循环控制线程 2] (CtrlLoopThreadCompTest.java:75) -----------------------开始执行线程方法 : 480d8b3c-80ba-4a8e-bce3-17debf5401d7
[INFO] 2022-02-20 22:26:35.276 [可循环控制线程 2] (CtrlLoopThreadCompTest.java:78) -----------------------结束执行线程方法 : 480d8b3c-80ba-4a8e-bce3-17debf5401d7
[DEBUG] 2022-02-20 22:26:35.377 [可循环控制线程 2] (CtrlLoopThreadComp.java:448) CtrlLoopThread [可循环控制线程 2] pause!!!
[DEBUG] 2022-02-20 22:26:35.378 [可循环控制线程 2] (CtrlLoopThreadComp.java:466) CtrlLoopThread [可循环控制线程 2] wake!!!
[INFO] 2022-02-20 22:26:35.378 [可循环控制线程 2] (CtrlLoopThreadCompTest.java:75) -----------------------开始执行线程方法 : 3d4e9b6b-6812-4ac0-b860-a4bb373b1769
[INFO] 2022-02-20 22:26:35.381 [可循环控制线程 2] (CtrlLoopThreadCompTest.java:78) -----------------------结束执行线程方法 : 3d4e9b6b-6812-4ac0-b860-a4bb373b1769
[DEBUG] 2022-02-20 22:26:35.481 [可循环控制线程 2] (CtrlLoopThreadComp.java:448) CtrlLoopThread [可循环控制线程 2] pause!!!
[DEBUG] 2022-02-20 22:26:35.482 [可循环控制线程 2] (CtrlLoopThreadComp.java:466) CtrlLoopThread [可循环控制线程 2] wake!!!
[INFO] 2022-02-20 22:26:35.587 [可循环控制线程 2] (CtrlLoopThreadCompTest.java:75) -----------------------开始执行线程方法 : a0eaf9dd-3c38-492d-9651-969395572a03
[INFO] 2022-02-20 22:26:35.590 [可循环控制线程 2] (CtrlLoopThreadCompTest.java:78) -----------------------结束执行线程方法 : a0eaf9dd-3c38-492d-9651-969395572a03
[DEBUG] 2022-02-20 22:26:35.691 [可循环控制线程 2] (CtrlLoopThreadComp.java:448) CtrlLoopThread [可循环控制线程 2] pause!!!
[DEBUG] 2022-02-20 22:26:35.692 [可循环控制线程 2] (CtrlLoopThreadComp.java:466) CtrlLoopThread [可循环控制线程 2] wake!!!
[INFO] 2022-02-20 22:26:35.696 [可循环控制线程 2] (CtrlLoopThreadCompTest.java:75) -----------------------开始执行线程方法 : 98a8e1c2-c666-489e-a1a3-77081be2fc97
[INFO] 2022-02-20 22:26:35.696 [可循环控制线程 2] (CtrlLoopThreadCompTest.java:78) -----------------------结束执行线程方法 : 98a8e1c2-c666-489e-a1a3-77081be2fc97
[INFO] 2022-02-20 22:26:36.106 [可循环控制线程 2] (CtrlLoopThreadCompTest.java:75) -----------------------开始执行线程方法 : c578f927-4b28-4623-80ae-414fafcf3fab
[INFO] 2022-02-20 22:26:36.108 [可循环控制线程 2] (CtrlLoopThreadCompTest.java:78) -----------------------结束执行线程方法 : c578f927-4b28-4623-80ae-414fafcf3fab
[DEBUG] 2022-02-20 22:26:36.209 [可循环控制线程 2] (CtrlLoopThreadComp.java:448) CtrlLoopThread [可循环控制线程 2] pause!!!
[DEBUG] 2022-02-20 22:26:36.210 [可循环控制线程 2] (CtrlLoopThreadComp.java:466) CtrlLoopThread [可循环控制线程 2] wake!!!
[DEBUG] 2022-02-20 22:26:36.210 [可循环控制线程 2] (CtrlLoopThreadComp.java:448) CtrlLoopThread [可循环控制线程 2] pause!!!
[DEBUG] 2022-02-20 22:26:36.210 [可循环控制线程 2] (CtrlLoopThreadComp.java:466) CtrlLoopThread [可循环控制线程 2] wake!!!
[DEBUG] 2022-02-20 22:26:36.210 [可循环控制线程 2] (CtrlLoopThreadComp.java:448) CtrlLoopThread [可循环控制线程 2] pause!!!
[DEBUG] 2022-02-20 22:26:36.212 [可循环控制线程 2] (CtrlLoopThreadComp.java:466) CtrlLoopThread [可循环控制线程 2] wake!!!
[INFO] 2022-02-20 22:26:36.212 [可循环控制线程 2] (CtrlLoopThreadCompTest.java:75) -----------------------开始执行线程方法 : fafbbd4b-398b-4531-986e-95fd665def07
[INFO] 2022-02-20 22:26:36.215 [可循环控制线程 2] (CtrlLoopThreadCompTest.java:78) -----------------------结束执行线程方法 : fafbbd4b-398b-4531-986e-95fd665def07
[DEBUG] 2022-02-20 22:26:36.316 [可循环控制线程 2] (CtrlLoopThreadComp.java:448) CtrlLoopThread [可循环控制线程 2] pause!!!
[DEBUG] 2022-02-20 22:26:36.317 [可循环控制线程 2] (CtrlLoopThreadComp.java:466) CtrlLoopThread [可循环控制线程 2] wake!!!
[INFO] 2022-02-20 22:26:36.317 [可循环控制线程 2] (CtrlLoopThreadCompTest.java:75) -----------------------开始执行线程方法 : af5269ce-a1d0-4c3b-961e-432f9a5b2579
[INFO] 2022-02-20 22:26:36.319 [可循环控制线程 2] (CtrlLoopThreadCompTest.java:78) -----------------------结束执行线程方法 : af5269ce-a1d0-4c3b-961e-432f9a5b2579
[DEBUG] 2022-02-20 22:26:36.419 [可循环控制线程 2] (CtrlLoopThreadComp.java:448) CtrlLoopThread [可循环控制线程 2] pause!!!
[DEBUG] 2022-02-20 22:26:36.420 [可循环控制线程 2] (CtrlLoopThreadComp.java:466) CtrlLoopThread [可循环控制线程 2] wake!!!
[INFO] 2022-02-20 22:26:36.420 [可循环控制线程 2] (CtrlLoopThreadCompTest.java:75) -----------------------开始执行线程方法 : 7bfbd960-7693-40d9-9786-f63a5bdc8bd5
[INFO] 2022-02-20 22:26:36.423 [可循环控制线程 2] (CtrlLoopThreadCompTest.java:78) -----------------------结束执行线程方法 : 7bfbd960-7693-40d9-9786-f63a5bdc8bd5
[DEBUG] 2022-02-20 22:26:36.524 [可循环控制线程 2] (CtrlLoopThreadComp.java:448) CtrlLoopThread [可循环控制线程 2] pause!!!
[DEBUG] 2022-02-20 22:26:36.525 [可循环控制线程 2] (CtrlLoopThreadComp.java:466) CtrlLoopThread [可循环控制线程 2] wake!!!
[DEBUG] 2022-02-20 22:26:37.059 [可循环控制线程 2] (CtrlLoopThreadComp.java:448) CtrlLoopThread [可循环控制线程 2] pause!!!
[DEBUG] 2022-02-20 22:26:37.060 [可循环控制线程 2] (CtrlLoopThreadComp.java:466) CtrlLoopThread [可循环控制线程 2] wake!!!
[INFO] 2022-02-20 22:26:37.066 [可循环控制线程 2] (CtrlLoopThreadCompTest.java:75) -----------------------开始执行线程方法 : 40a19c65-5eda-4387-95cf-6b6c1fc65d32
[INFO] 2022-02-20 22:26:37.068 [可循环控制线程 2] (CtrlLoopThreadCompTest.java:78) -----------------------结束执行线程方法 : 40a19c65-5eda-4387-95cf-6b6c1fc65d32
[DEBUG] 2022-02-20 22:26:37.169 [可循环控制线程 2] (CtrlLoopThreadComp.java:448) CtrlLoopThread [可循环控制线程 2] pause!!!
[DEBUG] 2022-02-20 22:26:37.170 [可循环控制线程 2] (CtrlLoopThreadComp.java:466) CtrlLoopThread [可循环控制线程 2] wake!!!
[INFO] 2022-02-20 22:26:37.172 [可循环控制线程 2] (CtrlLoopThreadCompTest.java:75) -----------------------开始执行线程方法 : c4882072-43a9-4c4a-8c28-16b05a04ad33
[INFO] 2022-02-20 22:26:37.174 [可循环控制线程 2] (CtrlLoopThreadCompTest.java:78) -----------------------结束执行线程方法 : c4882072-43a9-4c4a-8c28-16b05a04ad33
[DEBUG] 2022-02-20 22:26:37.274 [可循环控制线程 2] (CtrlLoopThreadComp.java:448) CtrlLoopThread [可循环控制线程 2] pause!!!
[DEBUG] 2022-02-20 22:26:37.275 [可循环控制线程 2] (CtrlLoopThreadComp.java:466) CtrlLoopThread [可循环控制线程 2] wake!!!
[INFO] 2022-02-20 22:26:37.277 [可循环控制线程 2] (CtrlLoopThreadCompTest.java:75) -----------------------开始执行线程方法 : e3ae9eae-8f88-4834-9e3a-fad9708a80d0
[INFO] 2022-02-20 22:26:37.279 [可循环控制线程 2] (CtrlLoopThreadCompTest.java:78) -----------------------结束执行线程方法 : e3ae9eae-8f88-4834-9e3a-fad9708a80d0
[DEBUG] 2022-02-20 22:26:37.380 [可循环控制线程 2] (CtrlLoopThreadComp.java:448) CtrlLoopThread [可循环控制线程 2] pause!!!
[DEBUG] 2022-02-20 22:26:37.595 [可循环控制线程 2] (CtrlLoopThreadComp.java:466) CtrlLoopThread [可循环控制线程 2] wake!!!
[DEBUG] 2022-02-20 22:26:37.595 [可循环控制线程 2] (CtrlLoopThreadComp.java:448) CtrlLoopThread [可循环控制线程 2] pause!!!
[DEBUG] 2022-02-20 22:26:37.595 [可循环控制线程 2] (CtrlLoopThreadComp.java:466) CtrlLoopThread [可循环控制线程 2] wake!!!
[INFO] 2022-02-20 22:26:37.595 [可循环控制线程 2] (CtrlLoopThreadCompTest.java:75) -----------------------开始执行线程方法 : 7a12ef36-3a99-450d-a370-e9b00ff9bf70
[INFO] 2022-02-20 22:26:37.596 [可循环控制线程 2] (CtrlLoopThreadCompTest.java:78) -----------------------结束执行线程方法 : 7a12ef36-3a99-450d-a370-e9b00ff9bf70
[INFO] 2022-02-20 22:26:37.625 [main] (CtrlLoopThreadCompTest.java:118)  ======> 执行完毕关闭线程
[DEBUG] 2022-02-20 22:26:37.625 [可循环控制线程 2] (CtrlLoopThreadComp.java:501) CtrlLoopThread [可循环控制线程 2] was interrupted during sleep
[DEBUG] 2022-02-20 22:26:37.625 [可循环控制线程 2] (CtrlLoopThreadComp.java:506) CtrlLoopThread [可循环控制线程 2] end!!!

除此之外, 还有一些功能

首先,CtrlLoopThreadComp.ofSupplier() 方法可以传入一个 Supplier 作为loop循环.

在循环中, 若是 Supplier 返回true, 则表示正常执行, 若是返回 false, 则会调用 falseFun 传入的 Consumer<CtrlComp> falseConsumer 方法.

若是在循环中执行报错, 则会调用 catchFun() 传入的BiConsumer<CtrlComp, RuntimeException> catchConsumer方法.

在这两个方法里面, 均有一个 CtrlComp 对象用于控制内置线程的相关状态.

使用方式如下.

@Test
public void asynchronousQueueTest3() {
   // 异步处理器
   final CtrlLoopThreadComp threadComp = CtrlLoopThreadComp.ofSupplier(() -> {
               String str = "";
               try {
                     str = blockingQueue.take();
               } catch (InterruptedException e) {
                  e.printStackTrace();
               }
               log.info("成功接收到了字符串 ==> {}", str);
               // 字符串转数字,转换不成功会抛出异常
               final int i = Integer.parseInt(str);
               // 偶数返回 true,奇数返回false
               return i % 2 == 0;
            })
            .setMillisecond(0)
            .setName("异步处理线程")
            // 执行出现异常则继续下一次执行
            .catchFun((ctrlComp, e) -> {
               log.error("执行异常的时候发生了错误 : {}", e.getMessage());
               // 执行错误后,暂停 300 毫秒
               ctrlComp.pause(300);
               // 执行错误后,结束循环线程
               // ctrlComp.endCtrlLoopThread();
            })
            .falseFun(ctrlComp -> {
               log.warn("执行 loop 循环的之后返回了 false");
               // 继续执行
               ctrlComp.continueNextLoop();
            });

   threadComp.start();

   // 即时往队列里面添加消息, 异步处理线程即时处理
   blockingQueue.add("23");
   blockingQueue.add("11");
   blockingQueue.add("32");
   blockingQueue.add("0");
   blockingQueue.add("哈哈");

   Throws.con(10, Thread::sleep).logThrowable();
}

执行日志

[INFO] 2022-02-20 22:48:59.137 [异步处理线程] (CtrlLoopThreadCompTest.java:272) 成功接收到了字符串 ==> 23
[WARN] 2022-02-20 22:48:59.137 [异步处理线程] (CtrlLoopThreadCompTest.java:289) 执行 loop 循环的之后返回了 false
[INFO] 2022-02-20 22:48:59.137 [异步处理线程] (CtrlLoopThreadCompTest.java:272) 成功接收到了字符串 ==> 11
[WARN] 2022-02-20 22:48:59.137 [异步处理线程] (CtrlLoopThreadCompTest.java:289) 执行 loop 循环的之后返回了 false
[INFO] 2022-02-20 22:48:59.137 [异步处理线程] (CtrlLoopThreadCompTest.java:272) 成功接收到了字符串 ==> 32
[INFO] 2022-02-20 22:48:59.137 [异步处理线程] (CtrlLoopThreadCompTest.java:272) 成功接收到了字符串 ==> 0
[INFO] 2022-02-20 22:48:59.137 [异步处理线程] (CtrlLoopThreadCompTest.java:272) 成功接收到了字符串 ==> 哈哈
[ERROR] 2022-02-20 22:48:59.137 [异步处理线程] (CtrlLoopThreadCompTest.java:282) 执行异常的时候发生了错误 : For input string: "哈哈"
[DEBUG] 2022-02-20 22:48:59.137 [异步处理线程] (CtrlLoopThreadComp.java:448) CtrlLoopThread [异步处理线程] pause!!!

CtrlLoopThreadComp 源码

就这一个类, 在开发的时候主键加入功能, 前前后后真的是更改了好久.

package com.github.cosycode.common.thread;

import lombok.Setter;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.util.function.BiConsumer;
import java.util.function.BooleanSupplier;
import java.util.function.Consumer;

/**
 * <b>Description : </b>  可控制的单循环线程, 和 CtrlLoopThread 的区别是对 Thread 采用了组合方式, 而不是继承方式
 * <p>
 * <b>设计如下: </b>
 * <br> <b>线程终止: </b> 只要内置线程调用 interrupt() 方法即视为线程需要终止.
 * <br> <b>线程等待和唤醒机制: </b> 因为线程调用 interrupt() 方法视为线程需要终止, 因此此处使用 wait + notify 来管理线程等待和唤醒.
 * </p>
 * <b>created in </b> 2020/8/13
 *
 * @author CPF
 * @since 1.0
 */
@Slf4j
@Accessors(chain = true)
public class CtrlLoopThreadComp implements AutoCloseable {

    /**
     * 抛出异常调用方法: 打印现场后继续下一次循环
     */
    public static final BiConsumer<CtrlComp, RuntimeException> CATCH_FUNCTION_CONTINUE = CtrlComp::logException;
    /**
     * 参照 Thread 中为每个线程生成一个 Id 的方法, 简单移植过来的.
     * 如果该类, 没有赋值 name, 则根据当前 Number 生成一个 name
     */
    private static int threadInitNumber;
    /**
     * 内置线程对象
     */
    protected final Thread thread;
    /**
     * 线程每次执行的函数, 如果函数返回false, 则线程循环结束
     */
    private final BooleanSupplier booleanSupplier;
    /**
     * 多长时间运行一次(while true 中的一个执行sleep多久)
     */
    @Setter
    private int millisecond;
    /**
     * 运行类
     */
    private final CtrlLoopRunnable ctrlLoopRunnable;
    /**
     * 处理函数对象, 此处使用 volatile 仅仅保证该引用的可见性
     */
    @SuppressWarnings("java:S3077")
    private volatile CtrlComp ctrlComp;
    /**
     * 返回 false 时调用方法
     */
    private Consumer<CtrlComp> falseConsumer;
    /**
     * 出错时的消费函数接口
     */
    private BiConsumer<CtrlComp, RuntimeException> catchConsumer;

    /**
     * @param booleanSupplier     运行函数, 如果运行中返回 false, 则暂停运行.
     * @param name                线程名称, 若为 empty, 则会自动取一个名字(以 CtrlLoopThreadComp- 开头)
     * @param continueIfException 发生异常时是否继续.
     * @param millisecond         两次循环之间间隔多久(毫秒)
     */
    protected CtrlLoopThreadComp(BooleanSupplier booleanSupplier, String name, boolean continueIfException, int millisecond) {
        this(booleanSupplier, null, continueIfException ? CATCH_FUNCTION_CONTINUE : null, name, millisecond);
    }

    /**
     * CtrlLoopThreadComp 主要构造方法
     * <p>
     * <br> CtrlLoopThreadComp 里面内置了一个线程, 线程会 每隔 millisecond 毫秒 循环调用 booleanSupplier 方法, 若 millisecond <= 0, 则表示不进行暂停.
     * <br> 当调用 booleanSupplier 结果返回 true, 则隔 millisecond 毫秒后继续调用 booleanSupplier 方法
     * <br> 当调用 booleanSupplier 结果返回 false, 则调用 falseConsumer 方法, 若 falseConsumer 为 null, 则不对返回值做任何处理, 继续下一次循环
     * <br> 当调用 booleanSupplier 时抛出运行时异常, 则调用 catchConsumer 方法, 若 catchConsumer 为 null, 则不对异常做任何处理, 等于将异常抛给虚拟机.
     * </p>
     *
     * @param booleanSupplier 运行函数(不可为 null)
     * @param falseConsumer   booleanSupplier 运行后返回 false 时调用函数
     * @param catchConsumer   booleanSupplier 运行后抛出 异常时 调用该函数
     * @param name            线程名称, 若为 empty, 则会自动取一个名字(以 CtrlLoopThreadComp- 开头)
     * @param millisecond     两次循环之间间隔多久(毫秒), 如果为 0 则表示不暂停.
     */
    protected CtrlLoopThreadComp(BooleanSupplier booleanSupplier, Consumer<CtrlComp> falseConsumer, BiConsumer<CtrlComp, RuntimeException> catchConsumer, String name, int millisecond) {
        this.booleanSupplier = booleanSupplier;
        this.falseConsumer = falseConsumer;
        this.catchConsumer = catchConsumer;
        this.millisecond = millisecond;
        this.ctrlLoopRunnable = new CtrlLoopRunnable();
        this.thread = new Thread(this.ctrlLoopRunnable, StringUtils.isBlank(name) ? "CtrlLoopThreadComp-" + nextThreadNum() : name);
    }

    /**
     * 参照 Thread 中为每个线程生成一个 Id 的方法, 简单移植过来的.
     *
     * @return 一个 Id 编号
     */
    private static synchronized int nextThreadNum() {
        return threadInitNumber++;
    }

    /**
     * @param runnable            运行函数
     * @param continueIfException 发生异常时是否继续.
     * @param millisecond         两次循环之间间隔多久(毫秒)
     * @return 创建的线程
     */
    public static CtrlLoopThreadComp ofRunnable(Runnable runnable, boolean continueIfException, int millisecond) {
        return new CtrlLoopThreadComp(() -> {
            runnable.run();
            return true;
        }, null, continueIfException, millisecond);
    }

    /**
     * @param runnable 运行函数
     * @return 创建的线程
     */
    public static CtrlLoopThreadComp ofRunnable(Runnable runnable) {
        return new CtrlLoopThreadComp(() -> {
            runnable.run();
            return true;
        }, null, false, 0);
    }

    /**
     * @param booleanSupplier     运行函数, 如果运行中返回 false, 则暂停运行.
     * @param continueIfException 发生异常时是否继续.
     * @param millisecond         两次循环之间间隔多久(毫秒)
     * @return 创建的线程
     */
    public static CtrlLoopThreadComp ofSupplier(BooleanSupplier booleanSupplier, boolean continueIfException, int millisecond) {
        return new CtrlLoopThreadComp(booleanSupplier, null, continueIfException, millisecond);
    }

    /**
     * @param booleanSupplier 运行函数, 如果运行中返回 false, 则暂停运行.
     * @return 创建的线程
     */
    public static CtrlLoopThreadComp ofSupplier(BooleanSupplier booleanSupplier) {
        return new CtrlLoopThreadComp(booleanSupplier, null, false, 0);
    }

    /**
     * loop函数, 添加一个函数, 方便子类继承
     *
     * @return 当前函数是否运行成功
     */
    protected boolean loop() {
        return booleanSupplier != null && booleanSupplier.getAsBoolean();
    }

    /**
     * 线程暂停
     */
    public void pause() {
        ctrlLoopRunnable.changeState(3, 0, 0);
    }

    /**
     * 线程暂停指定毫秒, 同Object.wait()一样, 若等待时间为0,则表示永久暂停。
     * 当线程正在暂停中时, 再次调用该方法, 线程在自动结束等待情况下, 将继续 wait 指定的时间
     *
     * @param waitTime 暂停的时间(毫秒), 若为0,则表示永久暂停。
     */
    public void pause(long waitTime) {
        ctrlLoopRunnable.changeState(3, waitTime, 0);
    }

    /**
     * 多少次运行之后暂停.
     *
     * @param loopTime 次数
     */
    public void pauseAfterLoopTime(int loopTime) {
        ctrlLoopRunnable.changeState(2, 0, loopTime);
    }

    /**
     * 线程恢复
     */
    public void wake() {
        ctrlLoopRunnable.changeState(1, 0, 0);
    }

    /**
     * 内置线程启动
     */
    public void start() {
        thread.start();
    }

    /**
     * 若没有启动的话, 则启动
     */
    public synchronized void startIfNotStart() {
        if (Thread.State.NEW == thread.getState()) {
            thread.start();
        }
    }

    /**
     * @return 内置线程状态
     */
    public Thread.State getThreadState() {
        return thread.getState();
    }

    /**
     * 线程启动或恢复
     */
    public void startOrWake() {
        final Thread.State state = thread.getState();
        switch (state) {
            case NEW:
                startIfNotStart();
                break;
            case BLOCKED:
            case RUNNABLE:
            case WAITING:
            case TIMED_WAITING:
                wake();
                break;
            case TERMINATED:
                log.warn("wrong invocation! CtrlLoopThread [{}] has ended!!!", thread.getName());
                break;
            default:
        }
    }

    /**
     * 通过 interrupt 停止循环线程, 线程将会在执行完当前循环之后, 自动停止
     */
    @Override
    public void close() {
        ctrlLoopRunnable.closeWhenThisLoopEnd();
    }

    /**
     * 当 loop 循环返回 false 时调用该线程.
     *
     * @param falseConsumer 返回 false 消费线程
     * @return 当前对象本身
     */
    public CtrlLoopThreadComp falseFun(Consumer<CtrlComp> falseConsumer) {
        this.falseConsumer = falseConsumer;
        return this;
    }

    /**
     * 设置出错线程, 如果发生异常, 则会调用该方法
     *
     * @param catchConsumer 出错消费线程
     * @return 当前对象本身
     */
    public CtrlLoopThreadComp catchFun(BiConsumer<CtrlComp, RuntimeException> catchConsumer) {
        this.catchConsumer = catchConsumer;
        return this;
    }

    /**
     * 获取线程名称
     *
     * @return 当前对象内线程名称
     */
    public String getName() {
        return thread.getName();
    }

    /**
     * 设置线程名称
     *
     * @param name 线程名称
     * @return 当前对象本身
     */
    public CtrlLoopThreadComp setName(String name) {
        thread.setName(name);
        return this;
    }

    /**
     * @return 返回唯一的控制器
     */
    public CtrlComp getCtrlComp() {
        if (ctrlComp == null) {
            synchronized (this) {
                if (ctrlComp == null) {
                    ctrlComp = new CtrlComp();
                }
            }
        }
        return ctrlComp;
    }

    /**
     * @param continueIfException 发生异常后是否继续下一次循环
     * @return 对象本身
     */
    public CtrlLoopThreadComp setContinueIfException(boolean continueIfException) {
        this.catchConsumer = CATCH_FUNCTION_CONTINUE;
        return this;
    }

    /**
     * <b>Description : </b> 可控制的循环线程的runnable内部类, 控制CtrlLoopThreadComp的执行方式
     * <p>
     * <b>created in </b> 2020/8/13
     *
     * @author CPF
     * @since 1.0
     **/
    private class CtrlLoopRunnable implements Runnable {
        /**
         * 用于线程启停的锁
         */
        private final Object lock = new Object();
        /**
         * 添加了线程状态 state
         * <p>
         * <br>添加 state 的好处是 使得更改 state 状态时变得容易理解, 最主要的目的就是方法 {@link CtrlLoopRunnable#changeState(int, long, int)}
         * <br>
         * <br> <b>0: </b>初始状态, 表示 state 还未被修改
         * <br> <b>1: </b>持续运行状态
         * <br> <b>2: </b>临时运行状态, 指定次数后转换为暂停状态, 此时 waitAfterLoopCount 有意义; 若 waitAfterLoopCount > 0, 则指定次数后转换为永久暂停状态, 否则马上转为永久暂停状态
         * <br> <b>3: </b>临时运行状态, 即将被暂停, 此时 waitTime 有意义; 若 waitTime>0, 则转为临时暂停状态, 否则转为永久暂停状态.
         * <br> <b>4: </b>临时暂停状态, 指定时间后被唤醒
         * <br> <b>5: </b>永久暂停状态, 需要使用 notify 唤醒
         * <br> <b>-1: </b>终止状态, 此时修改 state 已经没有意义
         */
        private volatile int state;
        /**
         * 线程结束标记, 若该标记为 true, 则运行完当前 loop 则直接结束线程.
         */
        private volatile boolean endFlag;
        /**
         * 线程正处在 loop 方法循环里面
         */
        private volatile int codeRunLocation;
        /**
         * 等待时间, 这里使用 wait 和 notify 对线程进行唤醒
         */
        private long waitTime;
        /**
         * 在多少次循环后暂停标记
         * <br> <b>-1: </b> 持续运行标记
         * <br> <b>0: </b> 运行至检查点, 触发暂停事件, 该值转为 -1
         * <br> <b>n(>0): </b> 运行至检查点, 该值减1
         */
        @Setter
        private int waitAfterLoopCount;

        /**
         * 执行当前 loop 结束后, 关闭线程.
         * <p>
         * 如果在 loop 中, 则通过更改标记位, 结束线程.
         * 如果不在 loop 中, 而是在线程循环控制处理逻辑内, 则直接调用 interrupt 关闭线程.
         */
        protected void closeWhenThisLoopEnd() {
            synchronized (lock) {
                this.endFlag = true;
                // 如果不是在 loop 的处理中, 则直接使用 interrupt 关闭线程
                if (codeRunLocation < 3 || codeRunLocation >= 5) {
                    thread.interrupt();
                }
            }
        }

        /**
         * <br> <b>-2: </b> 运行完这一次直接关闭线程
         * <br> <b>1: </b>持续运行状态
         * <br> <b>2: </b>临时运行状态, 指定次数后转换为暂停状态, 此时 waitAfterLoopCount 有意义
         * <br> <b>3: </b>临时运行状态, 即将被暂停, 此时 waitTime 有意义
         *
         * @param state             {@link CtrlLoopRunnable#state}
         * @param waitTime          等待时间, state=3时有意义, 若 waitTime>0, 则转为临时暂停状态, 否则转为永久暂停状态.
         * @param waitAfterLoopTime 循环指定次数后暂停, state=2时有意义, 若 waitAfterLoopCount > 0, 则指定次数后转换为永久暂停状态, 否则马上转为永久暂停状态
         */
        protected void changeState(final int state, final long waitTime, final int waitAfterLoopTime) {
            synchronized (lock) {
                switch (state) {
                    case 1:
                        this.waitTime = 0;
                        this.waitAfterLoopCount = 0;
                        lock.notifyAll();
                        break;
                    case 2:
                        this.waitTime = 0;
                        if (codeRunLocation == 2) {
                            // 此时运行在执行自定义函数之前, 次数判定之后, 因此此时需要将次数 - 1
                            this.waitAfterLoopCount = waitAfterLoopTime - 1;
                        } else {
                            this.waitAfterLoopCount = waitAfterLoopTime;
                        }
                        lock.notifyAll();
                        break;
                    case 3:
                        this.waitTime = waitTime;
                        this.waitAfterLoopCount = 0;
                        lock.notifyAll();
                        break;
                    default:
                }
                this.state = state;
            }
        }

        @Override
        @SuppressWarnings({"java:S1119", "java:S112"})
        public void run() {
            // 线程在启动之前, 可以不是0
            if (state == 0) {
                state = 1;
            }
            final String name = thread.getName();
            log.debug("CtrlLoopThread [{}] start!!!", name);
            outer:
            while (!thread.isInterrupted()) {
                // 临时运行状态, 指定次数后转换为暂停状态, 此时 waitAfterLoopCount 有意义
                if (state == 2) {
                    synchronized (lock) {
                        if (state == 2) {
                            if (waitAfterLoopCount > 0) {
                                waitAfterLoopCount--;
                            } else {
                                state = 3;
                                waitTime = 0;
                            }
                        }
                    }
                }
                codeRunLocation = 2;
                /* 这个地方使用额外的对象锁停止线程, 而不是使用线程本身的停滞机制, 保证一次循环执行完毕后执行停止操作, 而不是一次循环正在执行 booleanSupplier 的时候停止*/
                // 临时运行状态, 即将被暂停, 此时 waitTime 有意义, waitTime=0, 则转为永久暂停状态, waitTime>0, 则转为临时暂停状态
                if (state == 3) {
                    synchronized (lock) {
                        // 此处之所以使用 while 而不是 if 是因为想要在线程等待过程中或者结束后, 依然可以通过控制 state 使得线程可以再次陷入等待, 以及可以最佳等待时间.
                        while (state == 3) {
                            log.debug("CtrlLoopThread [{}] pause!!!", name);
                            try {
                                // 添加添加临时变量防止幻读
                                final long waitTmp = waitTime;
                                if (waitTmp > 0) {
                                    waitTime = 0;
                                    state = 4;
                                    lock.wait(waitTmp);
                                } else {
                                    state = 5;
                                    lock.wait();
                                }
                            } catch (InterruptedException e) {
                                log.debug("CtrlLoopThread [{}] was interrupted during waiting!!!", name);
                                thread.interrupt();
                                /* 线程中断即意味着线程结束, 此时跳出最外层循环 */
                                break outer;
                            }
                            log.debug("CtrlLoopThread [{}] wake!!!", name);
                        }
                    }
                }
                codeRunLocation = 3;
                // 线程关闭标记为true, 则关闭线程
                if (endFlag) {
                    break;
                }
                /* 这个地方是正式执行线程的代码 */
                try {
                    final boolean cont = loop();
                    codeRunLocation = 4;
                    // 当结果返回 false 同时 falseConsumer 不为 null 时, 调用 falseConsumer 方法, 否则不做任何处理, 继续下一次循环
                    if (!cont && falseConsumer != null) {
                        falseConsumer.accept(getCtrlComp());
                    }
                } catch (RuntimeException e) {
                    /* 如果发生异常则调用 catchConsumer, 若 catchConsumer 为 null, 则封装现场并抛出异常 */
                    if (catchConsumer != null) {
                        catchConsumer.accept(getCtrlComp(), e);
                    } else {
                        throw new RuntimeException(String.format("CtrlLoopThread [%s] processing exception, the thread stop!", name), e);
                    }
                }
                codeRunLocation = 5;
                // 线程关闭标记为true, 则关闭线程
                if (endFlag) {
                    break;
                }
                // 控制loop多久循环一次, 防止 CPU 过高占用
                if (millisecond > 0) {
                    try {
                        Thread.sleep(millisecond);
                    } catch (InterruptedException e) {
                        log.debug("CtrlLoopThread [{}] was interrupted during sleep", name);
                        thread.interrupt();
                    }
                }
            }
            log.debug("CtrlLoopThread [{}] end!!!", name);
            state = -1;
        }
    }

    /**
     * 当前类的控制器
     */
    public class CtrlComp {

        /**
         * 线程暂停指定毫秒, 同Object.wait()一样, 若等待时间为0,则表示永久暂停。
         * 当线程正在暂停中时, 再次调用该方法, 线程在自动结束等待情况下, 将继续 wait 指定的时间
         *
         * @param waitTime 暂停的时间(毫秒)
         */
        public void pause(long waitTime) {
            CtrlLoopThreadComp.this.pause(waitTime);
        }

        /**
         * 线程暂停
         */
        public void pause() {
            CtrlLoopThreadComp.this.pause();
        }

        /**
         * 继续下一次循环
         */
        public void continueNextLoop() {
        }

        /**
         * 终止内置线程
         */
        public void endCtrlLoopThread() {
            close();
        }

        /**
         * 打印异常
         *
         * @param e 发生异常
         */
        public void logException(RuntimeException e) {
            final String msg = String.format("CtrlLoopThread [%s] processing exception, continue to the next round", getName());
            log.error(msg, e);
        }

    }

}

git

相关源码在 github 和 gitee 上, 上面有最新的代码.

  • github: https://github.com/cosycode/common-lang
  • gitee: https://gitee.com/cosycode/common-lang

repo

同时我也将代码打包成 jar, 发布到 maven 仓库

Apache Maven

<dependency>
   <groupId>com.github.cosycode</groupId>
   <artifactId>common-lang</artifactId>
   <version>1.6</version>
</dependency>

gradle

implementation 'com.github.cosycode:common-lang:1.6'

该类完整类路径是 com.github.cosycode.common.thread.CtrlLoopThreadComp


若是有什么错误或者是有什么使用建议, 欢迎大家留言

;