首页 > 技术文章 > 为什么不推荐使用stop方法中断线程?

lichuanyan 2021-08-10 15:40 原文

使用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();;
    }

 

推荐阅读