[转]Java并发:终止线程和关闭线程池

时间:2024-03-12 17:46:00

原文链接:https://blog.csdn.net/jiq408694711/article/details/51002427

衔接上一篇的异步,新建线程池之后,记得关闭!要不线程池会一直保留在那里。

一、任务的取消
1.1标志位
 1 class ThreadOne implements Runnable
 2 {
 3     public volatile boolean isComplete = false;  
 4     @Override
 5     public void run() {     
 6        while(!isComplete)
 7        {
 8        }
 9     }  
10 }

 


这种方式有个很大的弊端:如果while循环中存在阻塞代码,更不巧的是这段阻塞代码不知道什么时候结束甚至可能永远也不结束,那么任务永远也不可能去检查isComple标志位,导致线程永远不可能结束。
总之要使用这种方法必须确保你的任务不可能阻塞,一定会检查到isComplete标志位,不管如何这种方式还是不建议使用。
 
1.2中断状态
1 public class Thread{
2 public void interrupt(){}    //中断目标线程,实际上就是设置中断状态为true
3 public boolean isInterrupted(){} //返回目标线程的中断状态
4 public static boolean interrupted(){} //清楚当前线程的中断状态,并返回它之前的值
5 }

 

设置中断状态
Java的中断是一种非抢占式的中断,Thread.interrupt()发出中断信号中断目标线程,实际上也只是设置中断标志位为true,对中断正确的理解是:并不真正中断正在运行的线程,而只是发出中断请求,由线程在下一个合适的时刻中断自己。中断是取消线程最合理的方式。
 
1.2.1 阻塞库响应中断
阻塞库方法在入口处就会去检查中断状态,在阻塞的过程中也会即可响应中断,一旦发现中断状态被设置,则:清除中断状态、抛出InterruptedException。JVM不能保证阻塞方法检测到中断的速度,但实际情况中响应中断的速度还是非常快的。
 
会检查中断状态并响应的阻塞库包括:
1、Thread.sleep()
2、Object.wait()
3、BlockingQueue.put()/take()
4、Thread.join()
 
不可响应中断的阻塞包括:
1、  同步IO,如InputStream.read()和OutputStream.write()
2、  异步IO,如Selector.select()
3、  等待获取某个内置锁
 
处理中断
阻塞函数抛出InterruptedException的同时已经清除了中断状态,对于应用程序来说有两种策略可以用于处理这个InterruptedException:
策略1:继续抛出此InterruptedException
策略2:退出前通过Thread.currentThread.interrupt()恢复中断
策略3:编写中断处理代码,保证中断得到完全处理
 1 class ThreadOne implements Runnable
 2 {  
 3     @Override
 4     public void run() {
 5        try{
 6            while(!Thread.currentThread().isInterrupted()){
 7                //dosomething...
 8                 Thread.sleep(20000);
 9            }
10        }catch(InterruptedExceptione){ 
11 System.out.println("run():interrupted while sleeping");
12 }
13     }
14 }

 

1.2.2 轮询中断状态
如果代码中不会调用可中断的阻塞方法,那么也可以通过在任务代码中轮询当前线程的中断状态来响应中断,只是这样中断响应性依赖于任务执行的时间。
1 class ThreadOne implements Runnable
2 {
3     @Override
4     public void run() {     
5        while(!Thread.currentThread().isInterrupted())
6        {
7        }
8     }  
9 }

 

1.3 Future.cancel
从Future的实现子类FutureTask针对cancel()方法的实现中可以看出,cancel()方法取消线程的方法是调用interrupt()方法尝试中断线程。
 
二、线程池的关闭
应用程序通常会创建线程池,维护多个线程,但是通常来说线程池的生命周期要比创建它们的程序生命周期要长,如果应用程序准备退出,那么线程池拥有的线程也需要结束,由于无法通过抢占式的方法来终止线程,因此需要它们能够自行结束。
 
线程池自身的生命周期管理方法
ExecutorService线程池就提供了shutdown和shutdownNow这样的生命周期方法来关闭线程池自身以及它拥有的所有线程。 
shutdown和shutdownNow这两个方法最先定义在ExecutorService接口中,我们可以在其子类ThreadPoolExecutor中找到对应的实现代码。
2.1 shutdown关闭线程池
方法定义:public void shutdown()
(1)线程池的状态变成SHUTDOWN状态,此时不能再往线程池中添加新的任务,否则会抛出RejectedExecutionException异常。
(2)线程池不会立刻退出,直到添加到线程池中的任务都已经处理完成,才会退出。 
注意这个函数不会等待提交的任务执行完成,要想等待全部任务完成,可以调用:
public boolean awaitTermination(longtimeout, TimeUnit unit)
 
2.2 shutdownNow关闭线程池并中断任务
方法定义:public List<Runnable> shutdownNow()
(1)线程池的状态立刻变成STOP状态,此时不能再往线程池中添加新的任务。
(2)终止等待执行的线程,并返回它们的列表;
(3)试图停止所有正在执行的线程,试图终止的方法是调用Thread.interrupt(),但是大家知道,如果线程中没有sleep 、wait、Condition、定时锁等应用, interrupt()方法是无法中断当前的线程的。所以,ShutdownNow()并不代表线程池就一定立即就能退出,它可能必须要等待所有正在执行的任务都执行完成了才能退出。