• java并发基础(四)--- 取消与中断


      《java并发编程实战》的第7章是任务的取消与关闭。我觉得这一章和第6章任务执行同样重要,一个在行为良好的软件和勉强运行的软件之间的最主要的区别就是,行为良好的软件能很完善的处理失败、关闭和取消等过程。

    一、任务取消

      在java中没有一种安全的抢占式(收到中断请求就立刻停止)的方式来停止线程,因此也没有安全的抢占式方法来停止任务。只有一些协作式的机制,比如设置请求已取消的标识。在下面的例子中,PrimeGenarator持续的列出素数,直到它被取消。cancel方法将设置cancelled标识,并且主循环在搜索下一个素数之前会检查这个标识,为了使这个过程可靠工作,cancelled必须是volatile类型。

    //使用volatile类型的域来保护取消状态
    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);
        }
    }

    测试,让genarator只执行一秒。

    public static void main(String[] args) throws Exception{
            PrimeGenerator generator = new PrimeGenerator();
            new Thread(generator).start();
            try {
                Thread.sleep(1000);
            }finally{
                generator.cancel();
            }
            
            System.out.println(generator.get());;
    }

    打印结果就不陈列了,无非就是一些素数而已。

    二、中断

      每一个线程都有一个boolean类型的中断状态。当中断线程时,这个线程的中断状态将被设置为true ,在Thread中包含了中断线程以及查询线程中断状态的方法,如下:

    public class Thread{
        //中断目标线程(但线程不会立刻停止,也就是不会抢占式停止)
        public void interrupt(){}
        //返回线程的中断状态 已中断:true 未中断:false
        public boolean isInterrupted(){}
        //清除当前线程的中断状态,并返回它之前的值,这是清除中断状态的唯一方法
        public static boolean interrupted(){}
    }

      如果一个线程被中断,会发生两件事情:1.清除中断状态 2.抛出InterruptedException异常,所以,有时候如果捕获了InterruptedException后还要有其他操作的话,要把当前线程中断:Thread.currentThread().interrupt();然后再做其他事。

      还有一点需要注意,调用interrupt并不意味着立即停止目标线程正在进行的工作,而只是传递了请求中断的消息。对中断操作的正确理解是:它并不会真正的中断一个正在运行的线程,而只是发出中断请求,然后由线程在下一个合适的时刻自己中断。

      现在我们回过头来看PrimeGenarator的例子,如果代码中出现了阻塞队列,那这种用volatile做标识的取消就可能会有问题,比如基于生产者和消费者模式的素数生成:

    //生产者
    class BrokenPrimeProducer extends Thread{
        
        private final BlockingQueue<BigInteger> queue;
        private volatile boolean cancelled = false;
        
        public BrokenPrimeProducer(BlockingQueue<BigInteger> queue) {
            this.queue = queue;
        }
        
        @Override
        public void run() {
            try {
                BigInteger p = BigInteger.ONE;
                while (!cancelled) {
                    queue.put(p = p.nextProbablePrime());
                }
            } catch (InterruptedException e) {
                
            }
        }
        
        public void cancel(){cancelled = true;}
    }

      消费者的代码就不写了,无非是从阻塞队列中取出素数。你看,这个时候,生产者线程生成素数,并将它们放入阻塞队列中,如果生产者的速度超过了消费者的处理速度,队列将被填满,put方法也会被阻塞,如果这时消费者想取消生产者这个任务就无济于事了,因为生产者阻塞在了put方法中。这个问题很好解决,使用中断来替代boolean标识。修改生产者代码:

    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;
                //条件改为当前线程是否中断
                while (!Thread.currentThread().isInterrupted()) {
                    queue.put(p = p.nextProbablePrime());
                }
            } catch (InterruptedException e) {
                /*允许线程退出*/
            }
        }
        
        public void cancel(){interrupt();}
    }

    消费者如果不再需要生产者,可以调用cancel方法,这样即使生产者处于阻塞状态,一样可以退出。由此可见,中断是实现取消的最合理方式

    三、中断策略

      最合理的中断策略是:尽快退出,并通知任务所有者该线程已经退出。因为任务一般不会在自己拥有的线程中执行,而是在比如线程池的服务中执行,对于线程池实现以外的代码,不应该对中断做过多干预。就比如当你为一户人家打扫房间时,即使主人不在,也不应该把这段时间内收到的邮件扔掉,而应该把邮件收起来,等主人回来以后再交给他们处理,尽管你可以阅读他们的杂志。这就是为什么大多数的api方法只是抛出InterruptedException作为中断响应,它们永远不会在在自己的线程中运行,因此它们为任务或者库代码实现了最合理的取消策略,也就是本段开头黑字部分,尽快退出,并把中断信息传递给调用者。另外,如果除了将InterruptedException传递给调用者外,还要执行其他操作,那么应该在捕获InterruptedException以后恢复中断状态:Thread.currentThread().interrupt();

      由于每个线程拥有各自的中断策略,因此除非你知道中断对该线程的含义,否则就不应该中断这个线程

    四、用Future实现取消

      ExecutorService.submit将返回一个Future来描述一个任务,Future拥有一个cancel方法,该方法带有一个boolean类型的参数mayInterruptIfRunning,为true表示该 能被中断,为false表示若任务还没启动,就不要运行它。

      示例:执行一个任务,在一段时间内有结果则返回,没有结果则取消。伪代码如下:

    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) {
            //finally中取消
        }catch (ExecutionException e) {
            // 重新抛出
            throw new  ExecutionException(e.getCause());
        }finally{
            //无论任务是抛异常还是正在运行,都将结束
            task.cancel(true);
        }
    }

     这一章就到这里吧,开发中应该用到的时候也不多,希望能在看别人代码的时候有所帮助。

               

      

      

      

      

  • 相关阅读:
    C# 读取JSON
    checkbox与说明文字无法对齐的问题
    C#判断一个string是否为数字
    C# 调用系统winmm.dll 播放音乐wav mp3
    C#导出EXCEL的几种方法
    C#遍历DataSet中数据的几种方法总结
    cookie 简单用法
    JQGrid 在页面加载时展开SubGrid
    Echarts 设置地图上文字大小及颜色
    Echarts 地图上显示数值
  • 原文地址:https://www.cnblogs.com/peterxiao/p/7624931.html
Copyright © 2020-2023  润新知