Java如何优雅的终止线程

了解线程

概念

线程 是程序中的执行线程。Java 虚拟机允许应用程序并发地运行多个执行线程

线程特点

  • 拥有状态,表示线程的状态,同一时刻中,JVM中的某个线程只有一种状态;
  • NEW

尚未启动的线程(程序运行开始至今一次未启动的线程)

  • RUNNABLE

可运行的线程,正在JVM中运行,但它可能在等待其他资源,如CPU。

  • BLOCKED

阻塞的线程,等待某个锁允许它继续运行

  • WAITING

无限等待(再次运行依赖于让它进入该状态的线程执行某个特定操作)

  • TIMED_WAITING

定时等待(再次运行依赖于让它进入该状态的线程在指定等待时间内某个特定操作)

  • TERMINATED

已退出的线程

  • 拥有优先级,决定线程的执行顺序;
  • 1至10之间的整数,默认数值为5。数值越高,执行的几率越高,优先级并不能决定线程的执行顺序
  • 子线程的优先级默认同父线程的一样。
  • 注意,当以下情况发生时,JVM将停止执行所有线程:
  • Runtime(运行时)的exit ()方法被调用并且该方法的调用被Security Manager所允许;
  • 所有的“非守护线程”都已停止运行(无论时正常停止还是一场停止);
  • 可以被标记为守护程序(Daemon)
  1. 守护线程的子线程仍是守护线程;
  2. 守护线程也就是“后台线程”,一般用来执行后台任务,而用户线程一般用户执行用户级任务。

终止线程的方法

  1. 使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。
    当run方法执行完后,线程就会退出。但有时run方法是永远不会结束的。如在服务端程序中使用线程进行监听客户端请求,或是其他的需要循环处理的任务。在这种情况下,一般是将这些任务放在一个循环中,如while循环。如果想让循环永远运行下去,可以使用while(true){……}来处理。但要想使while循环在某一特定条件下退出,最直接的方法就是设一个boolean类型的标志,并通过设置这个标志为true或false来控制while循环是否退出。下面给出了一个利用退出标志终止线程的例子。
    FlagExitThread.java
package com.rainmonth;

/**
 * Created by RandyZhang on 2017/3/23.
 */
public class FlagExitThread extends Thread {
    public volatile boolean isExit = false;

    public FlagExitThread(String name) {
        super(name);
    }

    @Override
    public void run() {
        while (!isExit) {
            System.out.println("I'm running");
        }

    }
}

DemoClient.java

package com.rainmonth;

/**
 * Created by RandyZhang on 2017/3/23.
 */
public class DemoClient {
    public static void main(String[] args) {
        System.out.println("优雅的终止线程实例");
      
        exitByFlag();
        // exitByInterrupt();
    }
  	
  	private static void exitByFlag() {
        FlagExitThread flagExitThread = new FlagExitThread(FlagExitThread.class.getSimpleName());
        flagExitThread.start();

        try {
            Thread.sleep(1000);
            flagExitThread.isExit = true;
            flagExitThread.join();
            System.out.println("线程退出");

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private static void exitByInterrupt() {
        FlagExitThread flagExitThread = new FlagExitThread(FlagExitThread.class.getSimpleName());
        System.out.println("flagExitThread running...");
        flagExitThread.start();

        try {
            Thread.sleep(1500);
            System.out.println("flagExitThread interrupted...");
            flagExitThread.interrupt();
            Thread.sleep(1500);
            System.out.println("stop application...");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}

输出结果:

打印了一大堆I’m running之后线程退出。

  1. 使用stop方法强行终止线程(这个方法不推荐使用,因为stop和suspend、resume一样,也可能发生不可预料的结果)。
    显示调用stop()方法。源码中关于stop() 的描述如下:
/*
 * 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.
 */

大意就是说,该方法的不安全性是固有的。调用stop()终止一个线程会释放它已经锁定的所有监视器(这将导致沿堆栈向上传播为检查的ThreadDeath异常被抛出),若此时之前受这些被释放的监视器保护的对象存在不一致性,并且这些对象对其他线程可见,这就会导致一些意想不到的后果。stop操作应该由哪些仅仅只需要修改某些代码就可以指示目标线程应该停止运行的代码来取代(方法一就是这种方式)。如果目标线程由于等待某一条件(如某个条件变量)等待很长时间,我们应该使用interrupt方法来中断该等待(方法三就是这种方式)。

  1. 使用interrupt方法中断线程。
    interrupt字面上是终止的意思,但不要试图通过调用interrupt来终止线程,因为有时即使你调用了该方法,线程仍然会继续执行,可以注释掉上面的exitByFlag(),开启exitByInterrupt() 方法,发现即使调用了interrupt()方法,仍在一直输出I’m running…(不同系统及CPU结果可能有所不同),可见采用interrupt方式也是不安全的。

总结

根据以上的分析,最值得推荐的方式是第一种,我们可以用共享变量(shared variable)的方式来设置标志,并发出信号,通知线程必须终止。当然对这个共享变量的操作我们必须保证是同步的。