使用stop方法中断线程会有以下两个问题
1、虽然能够释放自动持有的锁,比如synchronized的锁对象,但是无法调用unlock()方法释放ReentrantLock锁,所以可能导致死锁;
2、线程会被强制中断,无法确定结束位置,如果后续有业务强相关代码,可能出现各种问题。
如下示例代码
/** * 直接调用线程的stop方法 * @throws InterruptedException */ public static void badMethod() throws InterruptedException { Object lock1 = new Object(); Object lock2 = new Object(); Thread t1 = new Thread(() -> { synchronized (lock1) { synchronized (lock2) { try { LOG.info("t1获取到锁"); Thread.sleep(5000); // 此处为执行业务代码 LOG.info("t1结束"); } catch (InterruptedException e) { LOG.error(e.getMessage(), e); } } } }); t1.start(); Thread.sleep(1000); Thread t2 = new Thread(() -> { synchronized (lock1) { synchronized (lock2) { try { LOG.info("t2获取到锁"); Thread.sleep(5000); // 此处为执行业务代码 LOG.info("t2结束"); } catch (InterruptedException e) { LOG.error(e.getMessage(), e); } } } }); t2.start(); t1.stop(); }
打印的日志如下
[INFO][2021-08-10 15:27:37.962][Thread-1][com.lcy.demo01.Test01-lambda$0] t1获取到锁
[INFO][2021-08-10 15:27:38.966][Thread-2][com.lcy.demo01.Test01-lambda$1] t2获取到锁
[INFO][2021-08-10 15:27:43.974][Thread-2][com.lcy.demo01.Test01-lambda$1] t2结束
可以看出,t1确实释放了锁,但是无法确定结束执行的位置。
而如下代码,则演示了无法释放ReentrantLock导致的死锁。
/** * 调用stop方法,不会自动调用ReentrantLock的unlock方法释放锁,导致其他线程调用lock()时会死等下去 * * @throws InterruptedException */ public static void badMethodTest2() throws InterruptedException { Lock lock = new ReentrantLock(); Thread t1 = new Thread(() -> { lock.lock(); try { LOG.info("t1获取到锁"); Thread.sleep(5000); // 此处为执行业务代码 LOG.info("t1结束"); } catch (InterruptedException e) { LOG.error(e.getMessage(), e); } lock.unlock(); }); t1.start(); Thread.sleep(1000); Thread t2 = new Thread(() -> { LOG.info("t2开始执行"); lock.lock(); // 如果t1被中断,此处会因为没释放锁而死等下去 try { LOG.info("t2获取到锁"); Thread.sleep(5000); // 此处为执行业务代码 LOG.info("t2结束"); } catch (InterruptedException e) { LOG.error(e.getMessage(), e); } lock.unlock(); }); t2.start(); t1.stop(); }
此时打印的日志如下
[INFO][2021-08-10 15:29:48.146][Thread-1][com.lcy.demo01.Test01-lambda$2] t1获取到锁
[INFO][2021-08-10 15:29:49.158][Thread-2][com.lcy.demo01.Test01-lambda$3] t2开始执行
如果不使用stop来中断线程,那我们应该使用什么方式来替代stop呢?
这里有两种方式:
1、创建一个状态变量,在线程内循环检查变量状态,如果不满足,即结束执行当前业务
2、使用interrupt()方法
如下代码演示使用状态变量的方式
private static volatile boolean flag = true; /** * 推荐中断线程的方法1: * 定义一个状态变量,在线程内不断检查,如果状态不满足,则停止当前线程的业务 * @throws InterruptedException */ public static void goodMethod1() throws InterruptedException { Object lock1 = new Object(); Object lock2 = new Object(); Thread t1 = new Thread(() -> { synchronized (lock1) { synchronized (lock2) { try { LOG.info("t1获取到锁"); while(flag) { Thread.sleep(1000); // 此处为执行业务代码 } LOG.info("t1结束"); } catch (InterruptedException e) { LOG.error(e.getMessage(), e); } } } }); t1.start(); Thread.sleep(1000); Thread t2 = new Thread(() -> { synchronized (lock1) { synchronized (lock2) { try { LOG.info("t2获取到锁"); Thread.sleep(5000); // 此处为执行业务代码 LOG.info("t2结束"); } catch (InterruptedException e) { LOG.error(e.getMessage(), e); } } } }); t2.start(); flag = false; }
此时打印日志如下
[INFO][2021-08-10 15:33:10.565][Thread-1][com.lcy.demo01.Test01-lambda$6] t1获取到锁
[INFO][2021-08-10 15:33:12.589][Thread-1][com.lcy.demo01.Test01-lambda$6] t1结束
[INFO][2021-08-10 15:33:12.589][Thread-2][com.lcy.demo01.Test01-lambda$7] t2获取到锁
[INFO][2021-08-10 15:33:17.604][Thread-2][com.lcy.demo01.Test01-lambda$7] t2结束
如下代码演示使用interrupt方法来中断线程
/** * 推荐中断线程的方法2: * 使用interrupt * * Thread.sleep()方法是可以被中断的 * * @throws InterruptedException */ public static void goodMethod2() throws InterruptedException { Object lock1 = new Object(); Object lock2 = new Object(); Thread t1 = new Thread(() -> { synchronized (lock1) { synchronized (lock2) { try { LOG.info("t1获取到锁"); while(!Thread.currentThread().isInterrupted()) { Thread.sleep(1000); // 此处为执行业务代码 } LOG.info("t1结束"); } catch (InterruptedException e) { LOG.error(e.getMessage(), e); // 如果t1被中断,会进入此异常处理,同时t1的中断标志位会被重置 LOG.info("isInterrupted? {}", Thread.currentThread().isInterrupted()); } } } }); t1.start(); Thread.sleep(1000); Thread t2 = new Thread(() -> { synchronized (lock1) { synchronized (lock2) { try { LOG.info("t2获取到锁"); Thread.sleep(5000); // 此处为执行业务代码 LOG.info("t2结束"); } catch (InterruptedException e) { LOG.error(e.getMessage(), e); } } } }); t2.start(); t1.interrupt();; }
但是,使用interrupt方法来中断线程需要注意
1、如果被中断线程处于Thread.sleep()状态中,或者其他会抛出InterruptedException的状态中,调用该线程的interrupt方法后,会抛出InterruptedException,同时中断标志位会被重置
所以,以下针对两种情况(继续使用Thread.sleep、使用模拟业务代码而不是Thread.sleep),进行代码演示
/** * 使用interrupt * 业务代码继续使用Thread.sleep(),稍微修改下 * * @throws InterruptedException */ public static void goodMethod2Modify() throws InterruptedException { Object lock1 = new Object(); Object lock2 = new Object(); Thread t1 = new Thread(() -> { synchronized (lock1) { synchronized (lock2) { LOG.info("t1获取到锁"); while (!Thread.currentThread().isInterrupted()) { try { Thread.sleep(1000); // 此处为执行业务代码 } catch (InterruptedException e) { LOG.error(e.getMessage(), e); // 如果t1在sleep期间被中断,会进入此异常处理,同时t1的中断标志位会被重置 Thread.currentThread().interrupt(); // 重新设置中断标志位 } } LOG.info("t1结束"); } } }); t1.start(); Thread.sleep(1000); Thread t2 = new Thread(() -> { synchronized (lock1) { synchronized (lock2) { try { LOG.info("t2获取到锁"); Thread.sleep(5000); // 此处为执行业务代码 LOG.info("t2结束"); } catch (InterruptedException e) { LOG.error(e.getMessage(), e); } } } }); t2.start(); t1.interrupt();; }
/** * 使用interrupt * 业务代码改成不使用Thread.sleep() * * @throws InterruptedException */ public static void goodMethod2Test2() throws InterruptedException { Object lock1 = new Object(); Object lock2 = new Object(); Thread t1 = new Thread(() -> { synchronized (lock1) { synchronized (lock2) { LOG.info("t1获取到锁"); while (!Thread.currentThread().isInterrupted()) { for (int i = 0; i < 10000; i++) { if (i == 9000) { LOG.info("{}", i); } } } LOG.info("t1结束"); } } }); t1.start(); Thread.sleep(10); Thread t2 = new Thread(() -> { synchronized (lock1) { synchronized (lock2) { try { LOG.info("t2获取到锁"); Thread.sleep(5000); // 此处为执行业务代码 LOG.info("t2结束"); } catch (InterruptedException e) { LOG.error(e.getMessage(), e); } } } }); t2.start(); t1.interrupt();; }