在开始之前, 我们先来看以下代码会有什么问题?
- public class ThreadStopExample {
- public static void main(String[] args) throws InterruptedException {
- Thread t1 = new Thread(() -> {
- try {
- System.out.println("子线程开始执行");
- // 模拟业务处理
- Thread.sleep(1000);
- } catch (Exception e) { }
- // 伪代码: 重要的业务方法
- System.out.println("子线程的重要业务方法");
- });
- t1.start();
- // 让子线程先运行一点业务
- Thread.sleep(100);
- // 终止子线程
- t1.stop();
- // 等待一段时间, 确保子线程 "执行完"
- Thread.sleep(3000);
- System.out.println("主线程执行完成");
- }
- }
或许你已经发现了, 上面这段代码使用了 Thread.stop() 来终止线程, 在 Java 程序中是不允许这样终止线程的. 什么? 你问为什么不能这样?
首先来说 IDE 都会鄙视你了, 它会阻止你使用 Thread.stop() !
什么? 你不信. 那么来看这张图:
好吧, 那为什么不能这样用呢? 总得给我一个敷衍的理由吧?
问题一: 破坏了程序的完整性
其实是这样的, 以文章刚开头的那段代码来说, 它的执行结果是:
子线程开始执行
主线程执行完成
我们发现了一个惊天的大问题, 最重要的那段伪代码竟然没执行, 如下图所示:
可以看出使用 stop() 终止线程之后, 线程剩余的部分代码会放弃执行, 这样会造成严重的且不易被发现的惊天大 Bug, 假如没有执行的那段代码是释放系统资源的代码, 或者是此程序的主要逻辑处理代码. 这就破坏了程序基本逻辑的完整性, 导致意想不到的问题发生, 而且它还很隐秘, 不易被发现和修复.
有人说, 这还不简单, 我加个 finally 不就完了吗?
这??? 杠精哪都有, 今年特别多.
行, 既然这个说服不了你, 咱接着往下看.
问题二: 破坏了原子逻辑
我们知道在 Java 中 synchronized 属于独占式可重入悲观锁, 如果我们使用它修饰代码, 妥妥的多线程没问题, 但如果碰到 stop() 方法就不一定了, 直接来看代码吧.
- public class ThreadStopExample {
- public static void main(String[] args) throws InterruptedException {
- MyThread myThread = new MyThread();
- Thread t2 = new Thread(myThread);
- // 开启线程
- t2.start();
- for (int i = 0; i <10; i++) {
- Thread t = new Thread(myThread);
- t.start();
- }
- // 结束线程
- t2.stop();
- }
- /**
- * 自定义原子测试线程
- */
- static class MyThread implements Runnable {
- // 计数器
- int num = 0;
- @Override
- public void run() {
- // 同步代码块, 保证原子操作
- synchronized (MyThread.class) {
- // 自增
- num++;
- try {
- // 线程休眠 0.1 秒
- Thread.sleep(100);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- // 自减
- num--;
- System.out.println(Thread.currentThread().getName() + "| num=" + num);
- }
- }
- }
- }
以上程序的执行结果为:
- Thread-5 | num=1
- Thread-4 | num=1
- Thread-2 | num=1
- Thread-1 | num=1
- Thread-8 | num=1
- Thread-6 | num=1
- Thread-9 | num=1
- Thread-3 | num=1
- Thread-7 | num=1
- Thread-10 | num=1
从结果可以看出, 以上代码经过 synchronized 修饰的 ++ 和 -- 操作, 到最后打印的结果 num 竟然不是 0, 而是 1.
这是因为 stop() 方法会释放此线程中的所有锁, 导致程序执行紊乱, 破坏了程序的原子操作逻辑.
以上的这些问题, 导致了 JDK 废弃了 stop() 的方法, 它的废弃源码如下:
- /**
- * Forces the thread to stop executing.
- * <p>
- * If there is a security manager installed, its <code>checkAccess</code>
- * method is called with <code>this</code>
- * as its argument. This may result in a
- * <code>SecurityException</code> being raised (in the current thread).
- * <p>
- * If this thread is different from the current thread (that is, the current
- * thread is trying to stop a thread other than itself), the
- * security manager's <code>checkPermission</code> method (with a
- * <code>RuntimePermission("stopThread")</code> argument) is called in
- * addition.
- * Again, this may result in throwing a
- * <code>SecurityException</code> (in the current thread).
- * <p>
- * The thread represented by this thread is forced to stop whatever
- * it is doing abnormally and to throw a newly created
- * <code>ThreadDeath</code> object as an exception.
- * <p>
- * It is permitted to stop a thread that has not yet been started.
- * If the thread is eventually started, it immediately terminates.
- * <p>
- * An application should not normally try to catch
- * <code>ThreadDeath</code> unless it must do some extraordinary
- * cleanup operation (note that the throwing of
- * <code>ThreadDeath</code> causes <code>finally</code> clauses of
- * <code>try</code> statements to be executed before the thread
- * officially dies). If a <code>catch</code> clause catches a
- * <code>ThreadDeath</code> object, it is important to rethrow the
- * object so that the thread actually dies.
- * <p>
- * The top-level error handler that reacts to otherwise uncaught
- * exceptions does not print out a message or otherwise notify the
- * application if the uncaught exception is an instance of
- * <code>ThreadDeath</code>.
- *
- * @exception SecurityException if the current thread cannot
- * modify this thread.
- * @see #interrupt()
- * @see #checkAccess()
- * @see #run()
- * @see #start()
- * @see ThreadDeath
- * @see ThreadGroup#uncaughtException(Thread,Throwable)
- * @see SecurityManager#checkAccess(Thread)
- * @see SecurityManager#checkPermission
- * @deprecated This method is inherently unsafe. Stopping a thread with
- * Thread.stop causes it to unlock all of the monitors that it
- * has locked (as a natural consequence of the unchecked
- * <code>ThreadDeath</code> exception propagating up the stack). If
- * any of the objects previously protected by these monitors were in
- * an inconsistent state, the damaged objects become visible to
- * other threads, potentially resulting in arbitrary behavior. Many
- * uses of <code>stop</code> should be replaced by code that simply
- * modifies some variable to indicate that the target thread should
- * stop running. The target thread should check this variable
- * regularly, and return from its run method in an orderly fashion
- * if the variable indicates that it is to stop running. If the
- * target thread waits for long periods (on a condition variable,
- * for example), the <code>interrupt</code> method should be used to
- * interrupt the wait.
- * For more information, see
- * <a href="{@docRoot}/../technotes/guides/concurrency/threadPrimitiveDeprecation.html">Why
- * are Thread.stop, Thread.suspend and Thread.resume Deprecated?</a>.
- */
- @Deprecated
- public final void stop() {
- SecurityManager security = System.getSecurityManager();
- if (security != null) {
- checkAccess();
- if (this != Thread.currentThread()) {
- security.checkPermission(SecurityConstants.STOP_THREAD_PERMISSION);
- }
- }
- // A zero status value corresponds to "NEW", it can't change to
- // not-NEW because we hold the lock.
- if (threadStatus != 0) {
- resume(); // Wake up thread if it was suspended; no-op otherwise
- }
- // The VM can handle all thread states
- stop0(new ThreadDeath());
- }
可以看出 stop() 方法被 @Deprecated 注释修饰了, 而被此注解修饰的代码表示为过时方法, 不建议被使用. 从 stop() 的备注信息可以看出, 官方也不建议使用 stop() , 说它是一个非安全的方法.
正确终止线程
那如何终止线程呢? 这里提供 2 个正确的方法:
设置退出标识退出线程;
使用 interrupt() 方法终止线程.
1. 自定义退出标识
我们可以自定义一个布尔变量来标识是否需要退出线程, 实现代码如下:
- // 自定义退出标识退出线程
- static class FlagThread extends Thread {
- public volatile boolean exit = false;
- public void run() {
- while (!exit) {
- // 执行正常的业务逻辑
- }
- }
- }
可以看出我们使用了关键字 volatile 对线程进行了修饰, 这样就可以保证多线程的执行安全了, 在我们需要让线程退出时, 只需要把变量 exit 赋值为 true 就可以了.
2.interrupt 终止线程
当我们使用 interrupt() 方法时, 以上两个示例的执行结果就正常了, 执行代码如下:
- public class ThreadStopExample {
- public static void main(String[] args) throws InterruptedException {
- // 问题一: 破坏了程序的完整性
- Thread t1 = new Thread(() -> {
- try {
- System.out.println("子线程开始执行");
- // 模拟业务处理
- Thread.sleep(1000);
- } catch (Exception e) { }
- // 伪代码: 重要业务方法
- System.out.println("子线程的重要业务方法");
- });
- t1.start();
- // 让子线程先运行一点业务
- Thread.sleep(100);
- // 终止子线程
- t1.interrupt();
- // 等待一段时间, 确保子线程 "执行完"
- Thread.sleep(3000);
- System.out.println("主线程执行完成");
- // 问题二: 破坏了原子逻辑
- MyThread myThread = new MyThread();
- Thread t2 = new Thread(myThread);
- // 开启线程
- t2.start();
- for (int i = 0; i < 10; i++) {
- Thread t = new Thread(myThread);
- t.start();
- }
- // 结束线程
- t2.interrupt();
- }
- /**
- * 自定义原子测试线程
- */
- static class MyThread implements Runnable {
- // 计数器
- int num = 0;
- @Override
- public void run() {
- // 同步代码块, 保证原子操作
- synchronized (MyThread.class) {
- // 自增
- num++;
- try {
- // 线程休眠 0.1 秒
- Thread.sleep(100);
- } catch (InterruptedException e) {
- System.out.println(e.getMessage());
- }
- // 自减
- num--;
- System.out.println(Thread.currentThread().getName() + "| num=" + num);
- }
- }
- }
- }
以上程序的执行结果为:
子线程开始执行
子线程的重要业务方法
主线程执行完成
- sleep interrupted
- Thread-1 | num=0
- Thread-9 | num=0
- Thread-10 | num=0
- Thread-7 | num=0
- Thread-6 | num=0
- Thread-5 | num=0
- Thread-4 | num=0
- Thread-2 | num=0
- Thread-3 | num=0
- Thread-11 | num=0
- Thread-8 | num=0
可以看出以上的执行都符合我们的预期, 这才是正确的终止线程的方式.
总结
本文我们讲了线程的三种终止方式, 自定义退出标识的方式, 使用 stop() 的方式或 interrupt() 的方式. 其中 stop() 的方式会导致程序的完整性和原子性被破坏的问题, 并且此方法被 JDK 标识为过期方法, 不建议使用, 而 interrupt() 方法无疑是最适合我们的终止线程的方式.
来源: https://www.cnblogs.com/vipstone/p/12648933.html