线程的关闭正确打开方式
很多小伙伴都会想:纳尼?线程执行完毕之后就会结束呀,为什么还需要手动去关闭呢??
没错,一般情况下是无需手动去关闭的,但如果线程遇到某些特殊情况(阻塞、无限循环等)是不能自动终结的,这时候就需要我们知道如何正确的去关闭线程啦。
Thread中的stop()方法(不推荐)
为什么stop()方法不推荐我这里还要介绍呢?因为误用这个方法导致出现数据不一致的问题,所以我们还是有必要去了解一下的。我们先看下Thread类中stop()方法的源码:
@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()方法非常的暴力,会强行的将线程直接终止,并释放这个线程所持有的锁,可能前程执行到一半直接被强行结束了,可能导致数据不一致。
volatile修饰的标志位(推荐)
可以在代码中定义一个标记变量stopFlag ,标识线程是否要退出,程序中会根据标记变量的值判断是否继续执行,当希望线程终止的时候,直接修改标志位stopFlag为true即可正常结束。
@Data
public class MyTest extends Thread{
//设置线程终止标记标量,值为true的时候表示线程终止
private volatile boolean stopFlag = Boolean.FALSE;
@Override
public void run() {
while(true) {
if (Objects.equals(Boolean.TRUE, stopFlag)) {
System.out.println("exit thread!");
break;
}
System.out.println("run thread!");
}
}
public static void main(String[] args) {
MyTest myTest = new MyTest();
myTest.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
System.out.println(e);
}
//修改标志位的值,表示希望线程退出
myTest.setStopFlag(Boolean.TRUE);
}
}
线程中断(推荐)
首先介绍一下线程相关的几个方法:
interrupt() : 线程中断方法
该方法并不会使线程立即退出,而是给线程发送一个通知,修改线程的中断状态,线程接收到中断通知后如何处理,完全取决于线程自己。
isInterrupted() : 判断线程是否中断
public boolean isInterrupted() {
//值为false的时候表示不清除中断状态
return isInterrupted(false);
}
/*
* Tests if some Thread has been interrupted. The interrupted state
* is reset or not based on the value of ClearInterrupted that is
* passed.
*/
private native boolean isInterrupted(boolean ClearInterrupted);
interrupted() : 判断是否被中断,并清除中断状态
/**
* Tests whether the current thread has been interrupted. The
* <i>interrupted status</i> of the thread is cleared by this method. In
* other words, if this method were to be called twice in succession, the
* second call would return false (unless the current thread were
* interrupted again, after the first call had cleared its interrupted
* status and before the second call had examined it).
*
* <p>A thread interruption ignored because a thread was not alive
* at the time of the interrupt will be reflected by this method
* returning false.
*
* @return <code>true</code> if the current thread has been interrupted;
* <code>false</code> otherwise.
* @see #isInterrupted()
* @revised 6.0
*/
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
要想终止线程,可以调用interrupt()方法对目标线程设置中断状态,在程序中通过isInterrupted()方法查看线程是否被中断,若中断则将线程关闭。
听起来这是不是和第二种方法中讲的标识位是一样的呢?但线程中断拥有更强大的功能,若线程在执行过程中使用sleep()或wait()方法导致阻塞的时候,可手动的去捕获中断异常并进行手动处理,达到让线程正常关闭的目的。
@Data
public class MyTest extends Thread{
@Override
public void run() {
while(true) {
if (Thread.currentThread().isInterrupted()) {
System.out.println("exit thread!");
break;
}
try {
Thread.sleep(50);
System.out.println("sleep 50 ms then print run thread!");
} catch (InterruptedException e) {
System.out.println("InterruptedException when sleep");
//Thread中的sleep()方法抛出中断异常之后,会清除标记中断位,如果不加以处理,在下一次循环开始的时候isInterrupted()方法会返回false,就会无法捕获这个中断
Thread.currentThread().interrupt();
}
}
}
public static void main(String[] args) {
MyTest myTest = new MyTest();
myTest.start();
//先让程序执行两秒后再发出中断信息
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
System.out.println(e);
}
//发出中断信号
myTest.interrupt();
}
}
执行结果如图:
以上介绍了线程关闭的方法,可根据实际情况选择使用第二种(标志位)或者第三种方法(线程中断)关闭线程。
线程池的关闭方式
说起如何创建线程池,大家可以巴拉巴拉说出六七种,要在加上线程池的核心参数、等待队列、拒绝策略可以不喘气的跟面试官讲上半小时,但是同学们有没有关注如何去关闭一个线程池呢?
首先了解下线程池中线程的runState:
RUNNING: Accept new tasks and process queued tasks
SHUTDOWN: Don't accept new tasks, but process queued tasks
STOP: Don't accept new tasks, don't process queued tasks, and interrupt in-progress tasks
TIDYING: All tasks have terminated, workerCount is zero,the thread transitioning to state TIDYING will run the terminated() hook method
TERMINATED: terminated() has completed
接下来介绍下线程池关闭的方式:
shutdown()
shutdown()方法执行线程池将不在接收新的任务,且该方法会立马返回,但是该方法不会暴力的终止所有任务(正在处理及在队列中的任务),他会等待所有的任务都执行完之后在关闭线程池,但是他不会等所有的任务执行完之后再返回,可以简单的说shutdown()是发送一个简单的关闭信号而已。shutdwon()方法会将runState设置为SHUNDOWN。
shutdownNow()
shutdownNow()方法将不在接收新的任务,且不会等待线程池中的任务执行完,会将他们放在一个List队列里面。一般不推荐用此方法关闭线程池,一般用于一些紧急停止线程池的情况使用。shutdownNow会将线程的runState设置为STOP。
要想合理的关闭线程池一般会使用shutdown()方法结合以下几种方法一起使用:
-
boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException
一般调用shutdown()方法后,该方法表示在等待指定的时间之后再返回线程池关闭的状态,线程的状态有三种情况:- ture-表示在等待时间内,线程池已经关闭且线程池中所有任务已经执行完毕。
- false-表示在等待时间内,线程池内的任务没有执行完。
- InterruptedException,表示在等待时间内,线程池在执行剩余任务的时候发生中断异常。
-
boolean isShutdown()
用于判断线程池是否接收到关闭命令,即是否调用shutdown()或者shutdownNow命令。 -
boolean isTerminated()
用于判断线程池是否完全关闭,若shutdown()方法之后isTerminated()方法返回true则表示线程池中的所有任务执行完毕,线程池关闭。
总结
线程的关闭
关闭线程的方式推荐使用标志标量或者线程中断,使用方法中断的时候能够很好的处理线程中的发生阻塞等一些异常情况。
线程池的关闭
关闭线程一般使用shutdown()方法进行关闭,可使用awaitTermination()方法等待指定时间,根据实际的情况决定如何处理线程池关闭过程中可能出现的问题。