我们知道,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可以设置超时回收核心线程的时间