Java没有提供任何机制来安全地终止线程。提供了中断(一种协作机制),能够使一个线程终止另一个线程的当前工作。
行为良好的软件:能很完善地处理失败、关闭和取消等过程。
一个可取消的任务必须拥有取消策略,在这个策略中将详细地定义取消操作的“How”、“When”以及“What”,即其他代码如何(How)请求取消该任务,任务在何时(When)检查是否已经请求了取消,以及在响应取消请求时应该执行哪些(What)操作。
任务取消
如果外部代码能在某个操作正常完成之前将其置入“完成”状态,那么这个操作就可以称为可取消的(Cancellable)。
取消操作原因:
- 用户请求取消
- 有时间限制的操作
- 应用程序事件
- 错误
- 关闭
在Java中没有一种安全的抢占式方法来停止线程,因此也就没有安全的抢占式方法来停止任务。
使用volatile类型的域来保存取消状态
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
public class PrimeGenerator implements Runnable {
private final List<BigInteger> primes = new ArrayList<>();
/**
* 保持可见性
*/
private volatile boolean cancelled;
@Override
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<BigInteger> get() {
return new ArrayList<>(primes);
}
}
测试代码:
import java.math.BigInteger;
import java.util.List;
import static java.util.concurrent.TimeUnit.SECONDS;
public class PrimeGeneratorTest implements Runnable {
public List<BigInteger> aSecondOfPrimes() throws InterruptedException {
PrimeGenerator generator = new PrimeGenerator();
new Thread(generator).start();
try {
SECONDS.sleep(1);
} finally {
generator.cancel();
}
return generator.get();
}
@Override
public void run() {
try {
System.out.println(aSecondOfPrimes());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
new Thread(new PrimeGeneratorTest()).start();
}
}
中断
线程中断是一种协作机制,线程可以通过这种机制来通知另一个线程,告诉它在合适的或者可能的情况下停止当前工作,并转而执行其他的工作。
在Java中如果在取消之外的其他操作中使用中断,那么是不合适的,并且很难支撑起更大的应用。
每个线程都有一个boolean类型的中断状态。当中断线程时,这个线程的中断状态将被设置为true。
public class Thread{
public void interrupt(){...}
public boolean isInterrupted(){...}
public static boolean interrupted(){...}
}
如上代码:
interrupt( )能中断目标线程;
isInterrupted( )能返回目标线程的中断状态;
interrupted( )将清除当前的中断状态(中断标记重置为false),并返回它之前的值,这是清除中断状态的唯一方法。
调用interrupt( )并不意味着立即停止目标线程正在进行的工作,而只是传递了请求中断的消息。它并不会真正地中断一个正在运行的线程,而只是发出中断请求,然后由线程在下一个合适的时刻中断自己。
如果线程处于被阻塞状态(例如处于sleep, wait, join 等状态),那么线程将立即退出被阻塞状态,并抛出一个InterruptedException异常。可见,给目标线程发送中断还能够产生唤醒目标线程的效果。
使用interrupted( )时,它会清除当前线程的中断状态。如果在调用interrupted( )时返回了true,那么除非你想屏蔽这个中断,否则必须对它进行处理——可以抛出InterruptedException,或者通过再次调用interrupt( )来恢复中断状态。依照惯例,凡是抛出InterruptedException异常方法,通常会在其抛出该异常之前将当前线程的中断标记重置为false。
中断异常的处理方式(InterruptedException)
- 不捕获InterruptedException
- 捕获InterruptedException后重新将该异常抛出
- 捕获InterruptedException并在捕获该异常后中断当前线程
InterruptedException
抛出InterruptedException异常的同时它会将线程中断标志置为false。
public class InterrupTest implements Runnable {
@Override
public void run() {
try {
while (true) {
Boolean a = Thread.currentThread().isInterrupted();
System.out.println("in run() - about to sleep for 20 seconds-------" + a);
Thread.sleep(20000);
System.out.println("in run() - wake up");
}
} catch (InterruptedException e) {
//如果不加上这一句,那么c,d将会都是false,因为在捕捉到InterruptedException异常的时候就会自动的中断标志置为了false
Thread.currentThread().interrupt();
Boolean c = Thread.interrupted();
Boolean d = Thread.interrupted();
System.out.println("c = " + c);
System.out.println("d = " + d);
}
}
public static void main(String[] args) {
InterrupTest si = new InterrupTest();
Thread t = new Thread(si);
t.start();
//主线程休眠2秒,从而确保刚才启动的线程有机会执行一段时间
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("in main() - interrupting other thread");
//中断线程t
t.interrupt();
System.out.println("in main() - leaving");
}
}
output:
in run() - about to sleep for 20 seconds-------false
in main() - interrupting other thread
in main() - leaving
c = true
d = false
上述代码来源。
import java.util.concurrent.BlockingQueue;
public class TaskRunnable implements Runnable {
BlockingQueue<Task> queue;
@Override
public void run() {
try{
processTask(queue.take());
}catch(InterruptedException e){
// 恢复被中断的状态
Thread.currentThread().interrupt();
}
}
}
通常,中断是实现取消的最合理方式。
通过Future来实现取消
public static void timeRun(Runnable r,long timeout,TimeUnit unit)throws InterruptedException{
Future<?> task = taskExec.submit(r);
try{
task.get(timeout,unit);
}catch(TimeoutException e){
// 接下来任务将被取消
}catch(ExceptionException e){
// 如果在任务中抛出了异常,那么重新抛出该异常
throw launderThrowable(e.getCause());
}finally{
// 如果任务已经结束,那么执行取消操作也不会带来任何影响
task.cancel(true);// 如果任务正在运行,那么将被中断
}
}
处理不可中断的阻塞
如果一个线程由于执行同步的Socket I/O或者等待获得内置锁而阻塞,那么中断请求只能设置线程的中断状态,除此之外没有其他的任何作用。对于由于执行不可中断操作而被阻塞的线程,可以使用类似于中断的手段来停止这些线程,但这要求我们必须知道线程阻塞的原因。
只有通过execute提交的任务,才能将它抛出的异常交给未捕获异常处理器,而通过submit提交的任务,无论是抛出的未检查异常还是已检查异常,都将被认为是任务返回状态的一部分。