如果外部代码能在某个操作正常完成之前将其置入”完成”状态,那么这个操作就可以称为可取消的,取消某个操作的原因有很多:
用户请求取消: 用户点击图形界面程序中的”取消”按钮,或者通过管理接口来发出取消请求.
有时间限制操作:例如,某个应用程序需要在有限时间内搜索问题空间,并在这个时间内选择最佳的解决方案.当计时器超时时,需要取消所有正在搜索的任务.
应用程序事件: 例如,应用程序对某个问题空间进行分解并搜索,从而使不同任务都可以搜索问题空间中的不同区域.当其中一个任务找到了解决方案时,其他所有的任务都将被取消.
错误: 网页爬虫程序搜索相关页面,并将页面或摘要数据保存到硬盘,当一个爬虫任务发生错误时,那么所有的搜索任务都会取消,此时有可能会记录它们当前状态,以便稍后启动.
关闭: 当一个程序或服务关闭时,必须对正在处理和等待处理的工作执行某种操作,在平缓关闭过程中,当前正在执行的任务将继续执行直到完成,而在立即关闭的过程中,当前任务则有可能取消.
使用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<BigInteger>();
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<BigInteger>(primes);
}
}
一个仅运行一秒钟的素数生成器
static 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,那么可能会产生一个更严重的问题,任务永远不会检查取消标志,因此永不结束.
public class BrokenPrimeProducer extends Thread{
private final BlockingQueue<BigInteger> queue;
private volatile boolean cancelled;
BrokenPrimeProducer(BlockingQueue<BigInteger> queue){
this.queue = queue;
}
@Override
public void run() {
BigInteger p = BigInteger.ONE;
try{
while(!cancelled){
/**
* 当队列满了的时候
* 生产者阻塞,如果要取消任务,
* 调用cancel()这个方法是无法做到的,
* 如果消费者消费过慢,或者是迟迟不消费,
* 那么该任务就需要阻塞很久,直到消费者消费完毕才能取消.
*/
queue.put(p = p.nextProbablePrime());
}
}catch(InterruptedException e){}
}
public void cancel(){
cancelled = true;
}
static void consumePrimes(BlockingQueue<BigInteger> primes) throws InterruptedException{
BrokenPrimeProducer producer = new BrokenPrimeProducer(primes);
producer.start();
try{
while(needMorePrimes()){
consume(primes.take());
}
}finally{
producer.cancel();
}
}
private static void consume(BigInteger take) {
/**
* 假设消费时间很长很长
*/
}
private static boolean needMorePrimes() {
return false;
}
}
中断是实现取消最合理的方式
理解: 它并不会真正中断一个正在运行的线程,而只是发出中断请求,然后由线程在下一个合适的时刻中断自己.
public class PrimeProducer extends Thread{
private final BlockingQueue<BigInteger> queue;
PrimeProducer(BlockingQueue<BigInteger> queue){
this.queue = queue;
}
@Override
public void run() {
try{
BigInteger p = BigInteger.ONE;
/**
* 这里并不一定需要显式的检测,
* 但执行检测却会使PrimeProducer对中断具有更高的响应性
*/
while(!Thread.currentThread().isInterrupted())
queue.put(p = p.nextProbablePrime());
}catch(InterruptedException e){
/**
* 允许线程退出
*/
}
}
}