Java(JDK11)中让子线程随着主线程的结束一起被释放

189 阅读5分钟

我们知道,redisson看门狗机制,其实是在获取到锁以后,启动了一个新的线程去监控线程状态,看有没有被释放,这个状态被存在一个map里面。如果这个加锁的线程被释放掉了,我们的子线程就要一起被释放掉。假如我们没有redisson,我们可以怎么实现这个功能呢?

设计用例

我们需要设计一个简单的用例,一般我们用看门狗机制,是为了解决当前线程处理超时的问题。

  • 首先,我有一把会过期的锁,在主线程被使用。
  • 这把锁被加锁的以后,会倒计时过期,倒计时结束以后,主线程还没执行完,可以视为异常
  • 你的子线程需要帮你重置这个倒计时,让它不会在主线程执行完成以前失效
  • 当你的主线程执行完毕以后,可以主动释放掉子线程,不让它占用资源

通过标记处理这个问题

我们通过给主线程设置一个标记位,并且让子线程去监听这个标记位,从而打到续期的目的

public class WatchDogTest {
    public static void main(String[] args) {
        AtomicBoolean flag = new AtomicBoolean(false);
        System.out.println("我的主线程:" + Thread.currentThread().getName()+"已经拿到锁了!");

        // 定义一个子线程,
        Thread watchDogThread = new Thread(() -> {
            System.out.println("我的子线程:" + Thread.currentThread().getName());
            // 这里是在flag为false的时候,才会执行
            while (!flag.get()) {
                    // 这里可以放你的续期逻辑
                    System.out.println("当前线程为:" + Thread.currentThread().getName() + ",给主线程续期!");
                try {
                    // 每三秒拿一次
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            // 当跳出了循环,子线程就结束了
            System.out.println("子线程释放");
        });

        try {
            watchDogThread.start();
            // 这里主线程暂停10秒,模拟执行时间
            Thread.sleep(10000);
            System.out.println("主线程:"+Thread.currentThread().getName()+"执行完毕!");
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }finally {
            flag.set(true);
        }
    }
}
  • PS:这里的sleep()是会释放cpu的,所以不用担心while一直循环,但是sleep()不会释放线程的锁!

运行结果为:

我的主线程:main已经拿到锁了!
我的子线程:Thread-0
当前线程为:Thread-0,给主线程续期!
当前线程为:Thread-0,给主线程续期!
当前线程为:Thread-0,给主线程续期!
当前线程为:Thread-0,给主线程续期!
主线程:main执行完毕!
子线程释放

通过线程池实现

我们也可以通过创建一个固定长度的线程池来处理,这样可以不用自己定义标志位,只需要自己定义自己的业务逻辑就行

public class WatchDogThreadPoolTest {

    public static void main(String[] args) {
        ExecutorService threadPool = Executors.newFixedThreadPool(1);
        try {
            System.out.println("我的主线程:" + Thread.currentThread().getName()+"已经拿到锁了!");
            //执行子线程续期逻辑
            threadPool.execute(()->{
                System.out.println("我的子线程:" + Thread.currentThread().getName());
                // 检查共享标志位 返回Thread.currentThread().isInterrupted(),说明这个线程要被中断了
                while(!threadPool.isShutdown()){
                    System.out.println("我的子线程:" + Thread.currentThread().getName()+"给主线程续期逻辑");
                    // 每三秒拿一次
                    try {
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
                System.out.println("我的子线程:" + Thread.currentThread().getName()+"执行完毕!");
            });
            // 这里主线程暂停10秒,模拟执行时间
            Thread.sleep(10000);
            System.out.println("主线程:"+Thread.currentThread().getName()+"执行完毕!");
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }finally {
            // 释放线程池
            threadPool.shutdown();
        }
    }
}

运行结果:

我的主线程:main已经拿到锁了!
我的子线程:pool-1-thread-1
我的子线程:pool-1-thread-1给主线程续期逻辑
我的子线程:pool-1-thread-1给主线程续期逻辑
我的子线程:pool-1-thread-1给主线程续期逻辑
我的子线程:pool-1-thread-1给主线程续期逻辑
主线程:main执行完毕!
我的子线程:pool-1-thread-1执行完毕!

补充一些知识点

ExecutorService.shutdown()和ExecutorService.shutdownNow()有什么区别?

  • ExecutorService.shutdown(),会使当前的线程池停止接收新的线程,并且等待线程池中所有的线程执行完毕,才会真正的关闭线程。
  • ExecutorService.shutdownNow(),则会强制终止线程。
  • 可以结合awaitTermination()使用,设置一个等待时间,如果等待时间内,如果等待时间内执行完毕返回true,否则返回false。当返回false时再执行ExecutorService.shutdownNow(),相当于给了一个缓冲时间。

ExecutorService.execute()和ExecutorService.sumbit()的区别

  • execute(Runnable task)
    • 描述: execute 方法用于提交不需要返回结果的任务。它接受一个 Runnable 对象并在未来某个时间执行该任务。
    • 返回类型: 该方法没有返回值。
    • 异常处理: 如果任务在执行过程中抛出异常,线程池会捕获异常并处理,但不会向调用者报告异常。
  • submit(Runnable task)
    • 描述: submit 方法用于提交一个 Runnable 任务,并且可以获取一个 Future 对象。通过 Future 对象,调用者可以检查任务是否完成,还可以获取任务的返回结果(如果任务是 Callable 类型)。
    • 返回类型: 返回一个 Future<?> 对象。通过这个 Future 对象,调用者可以检查任务是否完成,取消任务,或者等待任务完成并获取结果。
    • 异常处理: 如果任务在执行过程中抛出异常,可以通过 Future.get() 方法捕获异常并处理。
  • submit(Callable task)
    • 描述: submit 方法还可以接受一个 Callable 对象并在未来某个时间执行该任务。Callable 可以返回一个结果或抛出一个异常。
    • 返回类型: 返回一个 Future 对象。通过这个 Future 对象,调用者可以获取任务的返回结果或异常。

线程池的核心线能被回收么?

  • 默认不能被回收,通过ThreadPoolExecutor的allowCoreThreadTimeOut可以设置超时回收核心线程的时间