并发编程之任务&线程的取消与关闭

任务或线程的取消与关闭

在向线程提交任务并且任务开始执行之后,通常任务执行完就自行停止和结束了,但是很多时候,我们希望在任务自行结束之前能提前终止,比如用户进行了取消操作等。具体来说,取消操作分为以下几种:

用户请求的取消操:比如用户按取消按钮,通过管理接口请求取消等;

超时停止:比如请求某个http请求,希望在指定时间还没返回结果时取消任务。

应用程序事件:比如寻找迷宫出口,如果有一个线程找到了出口,那么终止所有寻找出口的任务

运行时错误错误:比如任务在执行过程中出现某种错误,需要提供取消和停止任务线程的策略。

应用程序关闭:比如一个提供web服务的集群,某台机器性能表现非常差,需要关闭该机器以便进行排查。

     启动一个线程和任务比较简单,但是java没有提供一个安全,快速,可靠的机制来停止线程或任务。下面是几种典型的停止任务和线程的方法。

取消或关闭线程的方法或线程的方法

标志位取消。即提供一个Boolean类型的变量,通过设置该变量来停止任务

class CancelableTask extends Thread {
   praivate volatile boolean canceled =false;
   public void run() {
        while(!canceled ) {
              doSomething();
        }
   }
   public void cancel(){canceled  = true;}
}

    这里的中断标志位使用了volatile修饰。不用锁的目的是为了性能,而加volatile的目的是保证可见性,即每次读的时候总能读到最近更新的值,而不是被寄存器等缓存住的值。

中断

  方法1中,执行完doSomething()方法之后在循环到while中判断canceled的值。 这中方法可能能帮你解决大部分问题,但是有一种情况可能一份比较严重的问题:如果doSomething()被阻塞,那么永远无法执行下一次循环,当然也无法再判断canceled来取消任务了。比如在doSomething()方法中调用了BlockingQueue的put或take方法。BlockingQueue的take在BlockingQueue为空时一直阻塞直到queue中有数据为止。所以这种情况下通过标志位的终止策略失效了,同时也产生了线程活跃度问题。

  为解决这个问题,java提供了一种协作机制——中断策略来取消任务或线程。每一个线程都有一个中断状态(interrupted status),在中断的时候该中断状态为true。java线程提供了几个中断方法处理中断,interrupt()方法中断线程,调用该方法并不意味着目标线程必定停止了工作,它仅仅只是向目标线程传递了中断消息,是否停止工作还取决于目标线程获得中断消息之后的处理策略。isinterrupted()返回线程的中断状态。interrupted()方法清楚线程中断状态并且返回之前的中断状态,该方法也是唯一清除中断状态的方法。

  

class InterruptableTask extends Thread {
   
   public void run() {
        try {
              while(!thread.currentThread.isInterruped())
                   doSomeThing();
       } catch(InterruptException e) {
              exitThread();
       }
   }
   public void cancel() {intertupt();}
}

      中断请求只是向目标线程发生了消息,目标线程获得中断信号之后会做什么情取决了中断策略,因此定制线程的中断策略是必须的。尽可能迅速退出,如果需要的话进行清理,可能的话通知实体线程已退出。如果代码不是线程的拥有者(线程池的线程拥有者就是线程池),那么应该小心的保存中断状态。

通过Future取消

   

public void futureTask() throws ExecutionException {
      Future<?> task = taskExec.submit();
     try{
          task.get(timeout,unit);
     } catch(TimeoutExecption e){
       doSomeClear();
     }catch(ExecutionException e){
      throw ExecutionException ;
     } finally {
        task.cancel(true);
    }
}

 

 处理不可中断阻塞

   很多阻塞的库通过提前返回和抛出interruptExceptin异常来实现对中断的响应,但是并不是所有的阻塞方法和阻塞机制都响应中断。如果一个线程是同步socket I/O或等待内部锁而阻塞的,那么线程除了能移除中断状态之外,什么都做不了。对于那些不可中断活动所阻塞的线程,可以提供类型中断响应的方法来处理,但是必须先了解线程是由于什么原因阻塞的,才能对症下药。下面是处理不可中断阻塞的几种方法。

 1.java io中的同步io,最常见的阻塞IO是读取和写入socket,但是 inputstream和outputstream的read,write方法都是不响应中断的,但是通过关闭底层的socket,可以让read,write抛出一个socketException。

2. java nio中的同步io,中断一个等待iterruptibleChannel的线程,会导致抛出ClosedByInterruptException并关闭链路(也会导致其他线程在这条链路的阻塞并抛出ClosedByInterruptException)。关闭一个等待iterruptibleChannel导致多个阻塞在链路操作上的线程抛出AsynchronousCloseException。大多数标准的channel都实现了iterruptibleChannel。

3 Selector的异步I/O,如果一个线程阻塞于Selector.select方法,close方法会导致它通过抛出ClosedSelectorException提前返回。

4.获得锁。如果用的是内部锁,那么如果不能保证它最终能获得锁,你是没有办法终止它的。但是显式Lock类提供了LockInterruptible方法,在等待锁的同时也能够响应中断。

ExecutorService关闭

    ExectuorService提供了两个关闭方法:shutdown和shutdownNow。shutdown能在队列中的任务都处理完之后优雅的关闭,所以安全性高,缺点是响应慢。shutdownNow强行关闭ExectuorService,所以响应速度更快,缺点是不安全,可能破坏数据结构。

致命毒丸

      致命毒丸是一种关闭关闭生产者消费者模式的方式。在生产者队列中放入一个特殊对象,消费者获得特殊对象之后,开始关闭线程。这种方式要求消费者在放入致命毒丸之后,不应该在放入新的任务。致命毒丸必须在生产者消费者线程数据已知的情况下使用。在多生产者模式下,每个生产者放入致命毒丸,在消费者接受到第N(生产者的数目)个毒丸之后,开始执行关闭操作。在多消费者模式下,让生产者放入N(消费者的数目)毒丸,不过这种请况必须消费者是轮询处理任务的,否则不能保证每个消费者都得到毒丸。致命毒丸的一个缺点是在多消费者多生产者模式下不太好应用,有一定的局限性。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值