前面几章我们一直是创建和开启线程,而有时候我们要结束任务或线程,这并不是很容易的,因为,java 并没有提供任何机制来安全终止线程(在未来的jdk版本中会不会加入呢?)它提供了中断。这是一种机制,能够在一个线程终止另一个线程的工作。
任务取消
一种协作方式是设置某个“已请求取消”标识,而任务将定时查看该标志。下面程序,将持续枚举素数,直到它将被取消。cancle方法设置canceled标志,并且主循环在搜索下一个素数下一个之前,首先检查这个标志(canceled必须为volatile 原因请看
java内存模型)
public class PrimeGenerator implements Runnable {
private List<BigInteger> primes = new ArrayList<BigInteger>();
private volatile boolean canceled;
@Override
public void run() {
BigInteger bigInteger = BigInteger.ONE;
while (!canceled) {
bigInteger = bigInteger.nextProbablePrime();
synchronized (this) {
primes.add(bigInteger);
}
}
}
public void cancel() { this.canceled = true; }
public synchronized List<BigInteger> get() {
return new ArrayList<BigInteger>(primes);
}
}
现在我们要让一个素数生成器运行1秒后停止,(虽然不是很精确,因为每一条代码是有延时的),我们可以用try finally 来完成,以保证线程会停止,否则线程会一直消耗CPU时钟周期,导致JVM退出。
List<BigInteger> aSecondOfPrimes() throws InterruptedException {
PrimeGenerator generator = new PrimeGenerator();
new Thread(generator).start();
try {
Thread.sleep(1000);
} finally {
generator.cancel();
}
return generator.get();
}
中断
PrimeGenerator中的取消机制最终会使得搜索的素数任务退出,但是退出过程中需要花费一定的时间,然而,如果任务调度了一些阻塞方法,(BlockingQueue.put)那么可能产生一个问题——任务可能永远不会检查取消标示,永远不会结束。
为取保线程能退出,我们通常使用中断,当我们调用interrupt,并不意味着立即停止目标线程正在运行的线程,而只是传递了一个请求中断的信息,它会在线程下一个合适的的时刻中断自己。wait、sleep、join、将严格处理这种请求,当他们收到一个中断请求,或饿着开始执行时发现中断状态时,将抛出异常。
使用静态的interrupted时应该小心,因为它会清除当前线程的中断状态,如果返回true,除非你你想屏蔽这个中断,否则必须对它进行处理,抛出异常或者再次调用interrupt来恢复中断状态
try {
...
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
通常,我们用中断来取消任务比检查标记更好,是最合适的取消任务方式,我们看一个更加健壮的获得素数的类。
public class PrimeProducer extends Thread{
private final BlockingQueue<BigInteger> queue;
public PrimeProducer(BlockingQueue<BigInteger> queue) {
this.queue = queue;
}
@Override
public void run() {
try {
BigInteger p = BigInteger.ONE;
while(!Thread.currentThread().isInterrupted()) //①用线程的状态来检查
queue.put(p = p.nextProbablePrime());
} catch (InterruptedException e) {
//中断将线程退出
}
}
public void cancel() { interrupt(); }
}
我们分析下,在while时,我们有两次的检查中断,while中有一次,在执行put的时候有一次,这样我们比用flag标识有更高的效用性,通常,我们也是通过这种方式来取消线程的。