任务的取消

中断传递原理

Java中没有抢占式中断,就是武力让线程直接中断。

Java中的中断可以理解为就是一种简单的消息机制。某个线程可以向其他线程发送消息,告诉你“你应该中断了”。收到这条消息的线程可以根据这个消息做出反应。

意思是,不是你说让我停我就会停,我愿意停就停!

中断消息的传递其实就是通过Thread的一个布尔类型中断状态变量实现的。

发送中断请求时,线程的中断变量被设置为true:

  1. Thread t = new Thread(new Runnable() {
  2. @Override
  3. public void run() {
  4. //...
  5. }
  6. });
  7. t.start();
  8. //...
  9. t.interrupt();

任务线程中监听这个状态变量就知道是否有中断请求:

  1. @Override
  2. public void run() {
  3. while(!Thread.currentThread().isInterrupted()) {
  4.  
  5. }
  6. }

或:

  1. @Override
  2. public void run() {
  3. while(!Thread.interrupted()) {
  4.  
  5. }
  6. }

注意:阻塞方法一般都会抛出InterruptedException,这个异常一旦被try-catch到了,Thread的interrupt状态就会被自动清除(设置为false)。如果不得不try-catch(比如在Runnable的run方法中,或其他类似接口方法中),但是又不知道应该怎么处理中断异常时,一定要记得恢复中断状态:

  1. Thread.currentThread().interrupt();//重新标记为已中断

这样做的目的,是为了其他能够真的处理这个中断的地方能收到中断请求!

反过来说就是,把InterruptedException异常try-cath到了,但是又什么都不做,就像是拦截了别人的信件,自己看完之后扔垃圾桶了。这种行为破坏了导致中断机制。

Future的cancel

Future的cancel包含两层意思,一个是如果任务还没启动那么就别启动了,另外一个意思是启动了就中断。

  1. try {
  2. future.get(5, TimeUnit.SECONDS);
  3. } catch (ExecutionException | TimeoutException e) {
  4. e.printStackTrace();
  5. }finally {
  6. future.cancel(true);
  7. }

处理不可中断的任务

JDK中很多阻塞方法都会考虑到中断,对于不可中断的任务,比如Socket连接,我们能做的是改写Thread的interrupt,来手动做一些事情让任务“中断”掉。

  1. public class ReaderThread extends Thread{
  2.  
  3. private final Socket socket;
  4. private final InputStream in;
  5.  
  6. public ReaderThread(Socket socket) throws IOException {
  7. super();
  8. this.socket = socket;
  9. this.in = socket.getInputStream();
  10. }
  11.  
  12. @Override
  13. public void interrupt() {
  14. try {
  15. //关闭socket,与后面的try-catch配合使用
  16. this.socket.close();
  17. }catch(Exception e) {
  18. }finally{
  19. super.interrupt();
  20. }
  21. }
  22.  
  23. @Override
  24. public void run() {
  25. byte[] buf = new byte[1024];
  26. try {
  27. while(true) {
  28. int count = in.read(buf);//依赖socket.close异常跳出while
  29. if(count<0) {
  30. break;
  31. }else if(count>0) {
  32. processBuffer(buf,count);
  33. }
  34. }
  35. }catch(Exception e) {
  36.  
  37. }
  38. }
  39.  
  40. private void processBuffer(byte[] buf, int count) {
  41.  
  42. }
  43.  
  44. }

由此应该知道,在考虑中断的功能时,可以依赖Java的中断消息机制,但更多的是要根据实际的业务需求来合理利用中断机制。

线程服务的取消

如果我们对外提供了线程服务,那么就应该提供生命周期的方法,比如shutdown、cancel等。

基于前面提到的知识,可以用中断机制来关闭任务线程。但更重要的是线程的关闭不能影响业务。

比如WebLog应用,WebLog使用了一个阻塞队列来缓存WebLog日志,这样对个线程就能同时打日志了,另外有日志处理线程专门负责从队列中取日志来进行记录。这样一个简单的应用在取消的时候就不能盲目的“kill”掉,需要考虑:

1,关闭后已经在阻塞队列中的日志还是应该继续处理完;

2,不能继续接受新的日志存入到队列中了

  1. /**
  2. * @author huqiao
  3. */
  4. public class LogService {
  5.  
  6. private final BlockingQueue<String> queue;
  7. private final LoggerThread loggerThread;
  8. private final PrintWriter writer;
  9. private boolean isStoped = false;
  10. private int logCount = 0;
  11.  
  12. public LogService() {
  13. queue = new ArrayBlockingQueue<>(10);
  14. loggerThread = new LoggerThread();
  15. writer = new PrintWriter(System.out);
  16. }
  17. public void start() {
  18. loggerThread.start();
  19. }
  20.  
  21. public void stop() {
  22. isStoped = true;
  23. loggerThread.interrupt();//实现queue.take()抛异常的效果,促使任务线程赶紧去查看“isStoped”状态
  24. }
  25. public void log(String log) throws InterruptedException {
  26. synchronized (this) {
  27. if(isStoped) {
  28. //拒绝新日志写入
  29. throw new IllegalStateException(" logger is stoped");
  30. }
  31. logCount++;
  32. }
  33. queue.put(log);
  34. }
  35.  
  36. private class LoggerThread extends Thread{
  37.  
  38. @Override
  39. public void run() {
  40. try {
  41. while(true) {
  42. try {
  43. String log = null;
  44. synchronized (LogService.this) {
  45. if(isStoped && logCount == 0) {//logCount确保在stop之后还有机会处理接下来的日志
  46. break;
  47. }
  48. }
  49. log = queue.take();
  50. synchronized (LogService.this) {
  51. logCount--;
  52. }
  53. writer.write(log);
  54. } catch (InterruptedException e) {
  55. //忽略异常(try-catch到之后,中断状态已经被标记为false了,再take时就不会出错了),因为已经自己实现了中断机制
  56. }
  57. }
  58. }finally {
  59. writer.close();
  60. }
  61. }
  62. }
  63.  
  64. }

ExecutorService的关闭

shutdown:仅仅表示ExecutorService不再接受新的提交请求,它不会给子任务发中断请求;

shutdownNow:会尝试给任务线程发送中断请求,但如果子线程没有实现中断请求,那发了也没用,子线程照样一直跑;

无论是shutdown还是shutdownNow,如果线程任务没有实现中断,那就根本不会停止。showdownNow会多返回一个List,里面存着还没执行的任务。所以在实现ExecutorService的关闭时,一定要有下面任务线程的配合。

  1. public class ExecutorShutdownTest {
  2.  
  3. public static void main(String[] args) throws InterruptedException {
  4. ExecutorService executorService = Executors.newFixedThreadPool(1);
  5. for(int i= 0;i<1;i++) {
  6. executorService.execute(new Task(i+""));
  7. }
  8.  
  9. List<Runnable> taskList = executorService.shutdownNow();
  10. boolean successToTerminated = executorService.awaitTermination(10, TimeUnit.SECONDS);//这里等待10秒,能正常等待到任务线程退出
  11. System.out.println("successToTerminated=" + successToTerminated);
  12. for(Runnable runnable : taskList) {
  13. Task task = (Task)runnable;
  14. System.out.println(task.getName());
  15. }
  16. }
  17.  
  18. private static class Task implements Runnable{
  19.  
  20. private String name;
  21. public String getName() {
  22. return name;
  23. }
  24. public Task(String name) {
  25. this.name = name;
  26. }
  27. @Override
  28. public void run() {
  29. int i = 0;
  30. while(true) {
  31. try {
  32. Thread.sleep(1000);
  33. System.out.println("task " + name + " is running...");
  34. if(++i>3) {//3秒之后正常退出
  35. break;
  36. }
  37. } catch (InterruptedException e) {
  38. //e.printStackTrace();
  39. }
  40. }
  41. }
  42.  
  43. }
  44.  
  45. }

毒丸对象

书中介绍的这个毒丸对象是一种关闭子线程的巧妙办法。还是以前面的日志服务为例,客户代码想要子任务代码不要在继续跑了,那么可以给子任务扔一个“特殊的消息”。然后子任务会识别这个特殊的消息,让自己退出运行。

处理非正常的线程终止

线程服务中的多个线程有可能异常终止,我们应该要能即使的知道这样的事情。

在创建ExecutorService的时候可以指定线程的创建工程(ThreadFactory):

  1. public class UnCatchExceptionHandlerTest {
  2.  
  3. public static void main(String[] args) throws InterruptedException {
  4. ExecutorService service = Executors.newFixedThreadPool(10,new MyThreadFactory());
  5. service.execute(new Runnable() {
  6. @Override
  7. public void run() {
  8. int i = 0;
  9. System.out.println(100 / i);
  10. }
  11. });
  12. }
  13.  
  14. }

在这个线程工程中,可以为thread设置UncatchExceptionHandler:

  1. public class MyThreadFactory implements ThreadFactory{
  2.  
  3. @Override
  4. public Thread newThread(Runnable r) {
  5. Thread t = new Thread(r);
  6. t.setUncaughtExceptionHandler(new MyUncatchExceptionHandler());
  7. return t;
  8. }
  9.  
  10. }
  1. public class MyUncatchExceptionHandler implements UncaughtExceptionHandler{
  2.  
  3. @Override
  4. public void uncaughtException(Thread t, Throwable e) {
  5. System.out.println(t.getName() + " occur exception:" + e.getMessage());
  6. }
  7.  
  8. }

注意,这个方法只对execute提交的任务有效,用submit提交的任务,其异常还是会被Future.get()封装在ExecutionException中重新抛出。

Java并发编程实践读书笔记(4)任务取消和关闭的更多相关文章

  1. Java并发编程实践读书笔记(5) 线程池的使用

    Executor与Task的耦合性 1,除非线程池很非常大,否则一个Task不要依赖同一个线程服务中的另外一个Task,因为这样容易造成死锁: 2,线程的执行是并行的,所以在设计Task的时候要考虑到 ...

  2. Java并发编程实践(读书笔记) 任务执行(未完)

    任务的定义 大多数并发程序都是围绕任务进行管理的.任务就是抽象和离散的工作单元.   任务的执行策略 1.顺序的执行任务 这种策略的特点是一般只有按顺序处理到来的任务.一次只能处理一个任务,后来其它任 ...

  3. Java并发编程实践读书笔记(2)多线程基础组件

    同步容器 同步容器是指那些对所有的操作都进行加锁(synchronize)的容器.比如Vector.HashTable和Collections.synchronizedXXX返回系列对象: 可以看到, ...

  4. Java并发编程实践读书笔记(1)线程安全性和对象的共享

    2.线程的安全性 2.1什么是线程安全 在多个线程访问的时候,程序还能"正确",那就是线程安全的. 无状态(可以理解为没有字段的类)的对象一定是线程安全的. 2.2 原子性 典型的 ...

  5. Java并发编程实践读书笔记(3)任务执行

    类似于Web服务器这种多任务情况时,不可能只用一个线程来对外提供服务.这样效率和吞吐量都太低. 但是也不能来一个请求就创建一个线程,因为创建线程的成本很高,系统能创建的线程数量是有限的. 于是Exec ...

  6. Java并发编程实战 读书笔记(二)

    关于发布和逸出 并发编程实践中,this引用逃逸("this"escape)是指对象还没有构造完成,它的this引用就被发布出去了.这是危及到线程安全的,因为其他线程有可能通过这个 ...

  7. 《Java并发编程实战》第七章 取消与关闭 读书笔记

        Java没有提供不论什么机制来安全地(抢占式方法)终止线程,尽管Thread.stop和suspend等方法提供了这种机制,可是因为存在着一些严重的缺陷,因此应该避免使用. 但它提供了中断In ...

  8. Java并发编程实战 读书笔记(一)

    最近在看多线程经典书籍Java并发变成实战,很多概念有疑惑,虽然工作中很少用到多线程,但觉得还是自己太弱了.加油.记一些随笔.下面简单介绍一下线程. 一  线程与进程   进程与线程的解释   个人觉 ...

  9. Java并发编程艺术读书笔记

    1.多线程在CPU切换过程中,由于需要保存线程之前状态和加载新线程状态,成为上下文切换,上下文切换会造成消耗系统内存.所以,可合理控制线程数量. 如何控制: (1)使用ps -ef|grep appn ...

随机推荐

  1. Linux编程规范

    1)在使用C语言进行编程时,源文件都必须加---文件头 /******************************************************** *文件名:test.c *创 ...

  2. 二进制搭建kubernetes多master集群【二、配置flannel网络】

    上一篇我们已经搭建etcd高可用集群,参考:二进制搭建kubernetes多master集群[一.使用TLS证书搭建etcd集群] 此文将搭建flannel网络,目的使跨主机的docker能够互相通信 ...

  3. 2018.10.23 NOIP模拟 “新”的家园(缩图+dijksta/spfa)

    传送门 考试70分骗分写挂了=30分=全场最低. 哎今天230垫底了. 这题出的挺好. 对于非关键点直接缩点. 每次把要查的insertinsertinsert进缩好的图里面跑spfa/dijkstr ...

  4. 2018.09.15 秘密的牛奶管道SECRET(次小生成树)

    描述 约翰叔叔希望能够廉价连接他的供水系统,但是他不希望他的竞争对手知道他选择的路线.一般这样的问题需要选择最便宜的方式,所以他决定避免这种情况而采用第二便宜的方式. 现在有W(3 <= W & ...

  5. C++ 动态分配 和 内存分配和内存释放

    动态分配 动态分配可以说是指针的关键所在.不需要通过定义变量,就可以将指针指向分配的内存.也许这个概念看起来比较模糊,但是确实比较简单.下面的代码示范如何为一个整数分配内存: int *pNumber ...

  6. Java源码更改的方式

    1.找到要改的类所在包名地址. 比如标签名的更改: <s:debug></s:debug> (1)ctril+鼠标左键========双击标签,就会弹出标签所在的类的文本 (2 ...

  7. day08(File类 ,字节流)

    File类 构造方法 File(String path); FIle(String parent, String child);     File(File parent, String child) ...

  8. [php] try - catch exceptiong handler

    //http://stackoverflow.com/questions/1241728/can-i-try-catch-a-warningOne possibility is to set your ...

  9. [leetcode] 18. Length of Last Word

    这个题目很简单,给一个字符串,然后返回最后一个单词的长度就行.题目如下: Given a string s consists of upper/lower-case alphabets and emp ...

  10. Team Foundation Server 开发流程管理管理研讨会

    这周,和微软公司的朋友一起,受北京某金融企业邀请,为企业软件部门一个70多人的软件团队提供了一场基于Team Foundation Server的软件软件流程的技术研讨会.在研讨会中,培训基于微软Te ...