要启动一个Java线程是一件及愉快又轻松的事,只要start就行了。但是如果想要在线程未运行完毕的情况下取消线程的运行却不是一件容易的事

虽然Thread类提供了两个方法用于线程的停止(sopt)与挂起(suspend),但是在JDK中已经都@Deprecated了,所以作为开发者是不能使用这两个方法来终止线程的。

那么在JAVA中该如何快速,安全,可靠地终止一个线程呢?法宝只有一个:中断。

首先我们先来聊一下中断线程的应用场景或者说是为什么要中断线程。

1.为何要终止线程

用户请求取消   如用户搜索文件的时候发现一会儿就已经搜索到需要的文件了,那么用户就可以点击取消按钮来取消搜索文件的任务

有时间限制的操作   如多线程下载图片的时候如果有一张图片下载时间超时,那么就取消该下载图片的线程,以一张默认图片来代替就可以了,这样可以保证用户能够得到比较好的WEB体验。

发生错误   如开启多任务下载软件,当磁盘满的时候就会发生错误,那么就需要停止其他下载任务,并记录下当前的任务状态。

程序主动关闭线程   如开启多个线程需求问题的答案,如果有一个线程已经计算出答案了,那么就有必要主动停止其他正在计算的线程。还有比如,当关闭一个服务的时候,其他依赖于该服务的线程也得关闭,不然就会报错。

那么应该如何安全有效快速地取消或者停止线程呢?

2.关闭或者取消线程的N种法宝

检查取消标志

基本思想:在线程内设定一个死循环,不断检测线程取消标志,如果设置了这个标志,那么线程提前结束。

public class PrimeGenerator implements Runnable {
private final List primes = new ArrayList();
//注意使用volatile关键字,保证可见性
private volatile boolean cancelled;
public void run() {
BigInteger p = BigInteger.ONE;
while (!cancelled) {
p = p.nextProbablePrime();
synchronized (this) {
primes.add(p);
}
}
}
public void cancel() {
cancelled = true;
}
public synchronized List get() {
return new ArrayList(primes);
}
}

中断

虽然检查标志方法简单又好用,但是这种方法存在一个很严重的问题:任务可能永远不会检查取消标志,因此线程永远不会结束。

考虑如下一种生产者消费者场景:

Thread类停止 java java thread关闭_java 关闭按取消也关闭

生产者开辟一个线程用于将生产的产品放入到产品阻塞队列中,消费者开辟一个线程用于从产品阻塞队列中获取产品并消费。

如果此时消费者不需要消费产品了,那么消费者就有必要停止生产者生产产品。如果通过设置取消标志来取消生产者线程而且此时阻塞队列已经满了的话,那么生产者线程永远都不会检查到线程取消标志,因为生产者线程已经阻塞在put方法中了。这样就会导致生产者线程永远不会停止。

那么这种情况下就只有使用第二个法宝了:中断

Thread类中提供了三个方法用于中断:

public class Thread{
//中断目标线程
public void interrupt(){...}
// 返回目标线程的中断状态
public static boolean interrupted(){...}
// 清除当前线程的中断状态,并返回它之前的值
public boolean isInterrupted(){...}
}

不过对于中断需要理解一下几点:

1.调用interrupt方法并不意味着立即停止目标线程正在进行的工作,而只是传递了请求中断的消息。

2.interrupt并不是真正地中断一个正在运行的线程,而只是发出中断请求,然后由线程在下一个合适的时机中断自己

3.通常来讲,中断时实现取消的最合理的方式

少废话,上代码

public class PrimeProducer extends Thread {
private final BlockingQueue queue;
PrimeProducer(BlockingQueue queue) {
this.queue = queue;
}
public void run() {
try {
BigInteger p = BigInteger.ONE;
while (!Thread.currentThread().isInterrupted())
queue.put(p = p.nextProbablePrime());
} catch (InterruptedException consumed) {
/* Allow thread to exit */
}
}
public void cancel() {
interrupt();
}
}

通过Future来实现取消

使用ExecuorService.submit方法将返回一个Future来描述任务,Future有一个cancel方法。cancle方法有一个参数mayInterruptIfRunning,如果设置为true,那么就表示取消操作是否成功(这只是表示任务是否能够接受中断,而不是表示任务是否能够检测并处理中断)。如果为false,表示如果任务还没有运行,那么久不要运行它。

public class TimedRun {
private static final ExecutorService taskExec = Executors.newCachedThreadPool();
public static void timedRun(Runnable r,long timeout, TimeUnit unit)
throws InterruptedException {
Future> task = taskExec.submit(r);
try {
task.get(timeout, unit);
} catch (TimeoutException e) {
// 接下来任务将被取消
} catch (ExecutionException e) {
// 如果在任务执行和中抛出了异常,那么重新抛出该异常
throw launderThrowable(e.getCause());
} finally {
//如果任务已经结束,那么执行取消操作也不会带来任何影响
task.cancel(true); // 如果任务正在运行,那么将被中断
}
}
}

处理不可中断的阻塞                      在java库中,很多阻塞的方法都是通过提前返回或者是抛出InterruptedException来响应中断请求的,然而并非所有的可阻塞方法或者阻塞机制都能响应中断。

比如一个线程由于执行同步的Socket IO 或者等待获得内置锁而阻塞,那么中断请求只能设置线程的中断状态,除此之外没有其他任何作用,对于那些执行不可中断操作而被阻塞的线程,可以使用类似于中断的手段来停止这些线程,

但这要求我们必须知道线程阻塞的原因。

public class ReaderThread extends Thread {
private static final int BUFSZ = 512;
private final Socket socket;
private final InputStream in;
public ReaderThread(Socket socket) throws IOException {
this.socket = socket;
this.in = socket.getInputStream();
}
/**
* 既能处理标准的中断,也能关闭底层的socket.
* 因此,无论线程是在read方法中阻塞还是在某个可中断的阻塞方法中阻塞,都可以被中断并停止执行当前的任务
*/
public void interrupt() {
try {
socket.close();
} catch (IOException ignored) {
} finally {
super.interrupt();
}
}
public void run() {
try {
byte[] buf = new byte[BUFSZ];
while (true) {
int count = in.read(buf);
if (count < 0)
break;
else if (count > 0)
processBuffer(buf, count);
}
} catch (IOException e) { /* 允许线程退出 */
}
}
public void processBuffer(byte[] buf, int count) {
}
}