`
沉沦的快乐
  • 浏览: 56800 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

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

阅读更多

任务或线程的取消与关闭

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

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

超时停止:比如请求某个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(消费者的数目)毒丸,不过这种请况必须消费者是轮询处理任务的,否则不能保证每个消费者都得到毒丸。致命毒丸的一个缺点是在多消费者多生产者模式下不太好应用,有一定的局限性。

 

分享到:
评论

相关推荐

    JAVA多线程并发编程

    首先,Java并发编程的核心在于管理线程的并发执行。线程并发的使用可以显著提升程序处理能力,例如在服务器端处理大量用户请求时,如果每个请求都由单独的线程处理,那么处理速度将大大提高。但同时,多线程并发也会...

    JAVA并发编程实践-线程的关闭与取消-学习笔记

    在Java并发编程中,线程的关闭和取消是一项重要的任务,因为不正确的处理可能导致数据不一致、资源泄漏等问题。在Java中,强制停止线程并不是一个推荐的做法,因为这可能会导致系统状态的不稳定。传统的`Thread.stop...

    Java并发编程实战华章专业开发者书库 (Tim Peierls 等 美Brian Goetz).pdf

    《Java并发编程实战》是一本深入探讨Java平台并发编程的权威指南,由Tim Peierls等人与Brian Goetz合著,旨在帮助Java开发者理解和掌握在多线程环境中编写高效、安全的代码。这本书由拥有丰富经验的JDK并发大师及...

    Java并发编程实战

    第7章 取消与关闭 第8章 线程池的使用 第9章 图形用户界面应用程序 第三部分 活跃性、性能与测试 第10章 避免活跃性危险 第11章 性能与可伸缩性 第12章 并发程序的测试 第四部分 高级主题 第13章 显式锁 第...

    Java 并发编程实战

    第7章 取消与关闭 第8章 线程池的使用 第9章 图形用户界面应用程序 第三部分 活跃性、性能与测试 第10章 避免活跃性危险 第11章 性能与可伸缩性 第12章 并发程序的测试 第四部分 高级主题 第13章 显式锁 第...

    Java并发编程实践 PDF 高清版

    本书的读者是那些具有一定Java编程经验的程序员、希望了解Java SE 5,6在线程技术上的改进和新特性的程序员,以及Java和并发编程的爱好者。 目录 代码清单 序 第1章 介绍 1.1 并发的(非常)简短历史 1.2 线程的...

    go并发编程实践V2+源代码

    并发编程是现代软件开发中的一个重要概念,它允许程序同时执行多个任务,从而提高系统的效率和响应性。Go语言通过goroutines和channels这两大数据结构实现了轻量级线程和进程间的通信,使得并发编程变得简单且高效。...

    Java并发编程示例(三):线程中断

    在实际的并发编程中,线程中断常用于取消任务、响应外部事件或者优雅地关闭资源。它与`Thread.stop()`方法不同,后者是不推荐使用的,因为它可能导致数据损坏和死锁。线程中断是一种更加安全和推荐的方式来控制线程...

    Java并发编程(学习笔记).xmind

    Java并发编程 背景介绍 并发历史 必要性 进程 资源分配的最小单位 线程 CPU调度的最小单位 线程的优势 (1)如果设计正确,多线程程序可以通过提高处理器资源的利用率来提升系统吞吐率 ...

    C语言多线程编程之线程池

    在IT领域,尤其是在系统编程和并发处理中,C语言多线程编程是一个重要的主题。线程池是一种优化的线程管理技术,它提高了系统资源的利用率,并降低了线程的创建和销毁开销。本文将深入探讨C语言中的线程池及其相关...

    Java并发编程实战2019.zip

    第5章 基础构建模块 第6章 任务执行 第7章 取消与关闭 第8章 线程池的使用 第9章 图形用户界面应用程序 第10章 避免活跃性危险 第11章 性能与可伸缩性 第12章 并发程序的测试 第13章 显式锁 第14章 构建...

    Java并发编程实战-读书笔记

    《Java并发编程实战》个人读书笔记,非常详细: 1 简介 2 线程安全性 3 对象的共享 4 对象的组合 5 基础构建模块 6 任务执行 7 取消与关闭 8 线程池的使用 9 图形用户界面应用程序 10 避免活跃性危险 11 性能与可...

    Java并发编程实战1

    其中,任务执行、取消与关闭、线程池的使用等章节,讲解了如何有效地组织和管理线程,以避免并发问题。线程池允许我们预先创建一组线程,按需分配任务,从而提高系统效率并简化管理。 第三部分关注活跃性、性能和...

    JAVA并发编程实践_中文版(1-16章全)_1/4

    8.1 任务与执行策略问的隐性耦合 8.2 定制线程池的大小 8.3 配置threadpoolexecutor 8.4 扩展threadpoolexecutor 8.5 并行递归算法 第9章 gui应用程序 9.1 为什么gui是单线程化的 9.2 短期的gui任务 9.3 耗时gui任务...

    Java并发编程part2

    中文完整版的Java并发编程实践PDF电子书 作者:Brian Gogetz Tim Peierls Joshua Bloch Joseph Bowbeer David Holmes Doug Lea 译者:韩锴 方秒 目录 第1章 介绍 1.1 并发的(非常)简短历史 1.2 线程的优点 1.3 ...

    Java多线程编程实例

    在Java编程领域,多线程是一项至关重要的技术,它使得程序可以同时执行多...虽然年代久远,但这些原理和技巧在今天仍然适用,对于想要深入理解和掌握Java并发编程的开发者来说,这本书的源码实例无疑是宝贵的参考资料。

    go并发编程实战

    《Go并发编程实战》这本书是针对Go语言并发特性的深入探讨与实践指南。Go语言以其内置的并发原语,如goroutines和channels,为开发者提供了一种高效且简洁的并发编程方式。在现代多核处理器环境下,理解并掌握并发...

    java并发(二十四)多线程结果组装

    在Java编程中,多线程是并发处理任务的关键技术,特别是在高性能、高并发的应用场景下。本篇将探讨“多线程结果组装”的主题,它涉及到如何在多个并发执行的任务完成后,有效地收集并整合这些任务的结果。这个过程...

Global site tag (gtag.js) - Google Analytics