多线程进阶004 之 任务取消(一)

来源:互联网 发布:掌上电力提示网络异常 编辑:程序博客网 时间:2024/05/22 17:34

如果外部代码能在某个操作正常完成之前将其置入”完成”状态,那么这个操作就可以称为可取消的,取消某个操作的原因有很多:
用户请求取消: 用户点击图形界面程序中的”取消”按钮,或者通过管理接口来发出取消请求.
有时间限制操作:例如,某个应用程序需要在有限时间内搜索问题空间,并在这个时间内选择最佳的解决方案.当计时器超时时,需要取消所有正在搜索的任务.
应用程序事件: 例如,应用程序对某个问题空间进行分解并搜索,从而使不同任务都可以搜索问题空间中的不同区域.当其中一个任务找到了解决方案时,其他所有的任务都将被取消.
错误: 网页爬虫程序搜索相关页面,并将页面或摘要数据保存到硬盘,当一个爬虫任务发生错误时,那么所有的搜索任务都会取消,此时有可能会记录它们当前状态,以便稍后启动.
关闭: 当一个程序或服务关闭时,必须对正在处理和等待处理的工作执行某种操作,在平缓关闭过程中,当前正在执行的任务将继续执行直到完成,而在立即关闭的过程中,当前任务则有可能取消.

使用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){            /**             * 允许线程退出             */        }    }}
原创粉丝点击