加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 编程开发 > Java > 正文

<>第七章:取消和关闭

发布时间:2020-12-14 06:20:01 所属栏目:Java 来源:网络整理
导读:Java没有提供任何机制来安全地终止线程,虽然Thread.stop和suspend等方法提供了这样的机制,但是存在严重的缺陷,应该避免使用这些方法。但是Java提供了中断Interruption机制,这是一种协作机制,能够使一个线程终止另一个线程的当前工作。 这种协作方式是必

Java没有提供任何机制来安全地终止线程,虽然Thread.stop和suspend等方法提供了这样的机制,但是存在严重的缺陷,应该避免使用这些方法。但是Java提供了中断Interruption机制,这是一种协作机制,能够使一个线程终止另一个线程的当前工作。

这种协作方式是必要的,我们很少希望某个任务线程或者服务立即停止,因为这种立即停止会时某个共享的数据结构处于不一致的状态。相反,在编写任务和服务的时候可以使用一种协作方式:当需要停止的时候,它们会先清除当前正在执行的工作,然后再结束。

7.1 ?任务取消

如果外部代码能够在某个操作正常完成之前将其置于 完成 状态,那么这个操作就可以称为可取消的Cancellable

其中一种协作机制是设置一个取消标志Cancellation Requested标志,而任务定期查看该标志。

  1. ????
  2. ??
  3. ????
  4. ?????primes?=?();??
  5. ????
  6. ??
  7. ????
  8. ????????BigInteger?p?=?BigInteger.ONE;??
  9. ????????
  10. ????????????p?=?p.nextProbablePrime();??
  11. ????????????
  12. ????????????????primes.add(p);??
  13. ????????????}??
  14. ????????}??
  15. ????}??
  16. ??
  17. ????
  18. ????????cancelled?=?
  19. ????}??
  20. ??
  21. ?????get()?{??
  22. ????????(primes);??
  23. ????}??
  24. ??
  25. ?????aSecondOfPrimes()?
  26. ????????PrimeGenerator?generator?=?
  27. ????????exec.execute(generator);??
  28. ????????
  29. ????????????SECONDS.sleep(
  30. ????????}?
  31. ????????????generator.cancel();??
  32. ????????}??
  33. ????????
  34. ????}??
  35. }??

?在Java的API或语言规范中,并没有将中断与任何语义关联起来,但实际上,如果在取消之外的其他操作中使用中断,那么都是不合适的,并且很难支撑起更大的应用。

每个线程都有一个boolean类型的中断状态。但中断线程时,这个线程的中断状态将被设置成true。

Thread中的三个方法:

public void interrupt() ? 中断一个线程

public boolean isInterrupted() ?获取中断标志,判断是否中断

public static boolean interrupted() ?清楚中断状态,并返回它之前的状态值

线程在阻塞状态下发生中断的时候会抛出InterruptedException,例如Thread.sleep(),Thread.wait(),Thread.join()等方法。

当线程在非阻塞状态下中断的时候,它的中断状态将被设置,然后根据检查中断状态来判断是否中断。

调用interrupt并不意味着立即停止目标线程正在进行的工作,而只是传递了请求中断的消息,换句话说,仅仅修改了线程的isInterrupted标志字段。

通常,中断时实现取消的最合理方式。

  1. ?????queue;??
  2. ??
  3. ????PrimeProducer(BlockingQueue?queue)?{??
  4. ????????
  5. ????}??
  6. ??
  7. ????
  8. ????????
  9. ????????????BigInteger?p?=?BigInteger.ONE;??
  10. ????????????
  11. ????????????????queue.put(p?=?p.nextProbablePrime());??
  12. ????????}?
  13. ????????????
  14. ????????}??
  15. ????}??
  16. ??
  17. ????
  18. ????????interrupt();??
  19. ????}??
  20. }??

7.1.3 ?响应中断

只有实现了线程中断策略的代码才可以屏蔽中断请求,在常规任务和库代码中都不应该屏蔽中断请求。

两种方法响应中断:

* 传递异常InterruptedException
* 恢复中断状态,从而使调用栈中上层代码能够对其进行处理
不可取消的任务在退出前恢复中断标志
  1. ?????queue)?{??
  2. ????????
  3. ????????
  4. ????????????
  5. ????????????????
  6. ????????????????????
  7. ????????????????}?
  8. ????????????????????interrupted?=?
  9. ????????????????????
  10. ????????????????}??
  11. ????????????}??
  12. ????????}?
  13. ????????????
  14. ????????????????Thread.currentThread().interrupt();??
  15. ????????}??
  16. ????}??
  17. ??
  18. ????
  19. ????}??
  20. }??
?
7.1.5 ?定时任务,通过Future来实现取消:
除非你清楚线程的中断策略,否则不要中断线程,那么在神马情况下调用cancel可以将参数指定为true呢。
如果任务的线程是由标准的Executor创建的,那么可以设置mayInterruptIfRunning。
  1. ????
  2. ??
  3. ????
  4. ????????????
  5. ????????Future?task?=?taskExec.submit(r);??
  6. ????????
  7. ????????????task.get(timeout,?unit);??
  8. ????????}?
  9. ????????????
  10. ????????}?
  11. ????????????
  12. ????????????
  13. ????????}?
  14. ????????????
  15. ????????????task.cancel(
  16. ????????}??
  17. ????}??
  18. }??
?
7.1.6 ?处理不可中断的阻塞
对于这些线程,中断请求只能设置线程的中断状态,除此之外没有其他任何作用。
我们可以使用类似中断的手段来停止这些线程,但这要求知道线程阻塞的原因。
通过newTaskFor将非标准的取消操作封装在一个任务中:
?
    ??{??
  1. ????
  2. ????
  3. ??
  4. ????
  5. ????????socket?=?s;??
  6. ????}??
  7. ??
  8. ????
  9. ????????
  10. ????????????
  11. ????????????????socket.close();??
  12. ????????}?
  13. ????????}??
  14. ????}??
  15. ??
  16. ?????newTask()?{??
  17. ????????(
  18. ????????????
  19. ????????????????
  20. ????????????????????SocketUsingTask.
  21. ????????????????}?
  22. ????????????????????
  23. ????????????????}??
  24. ????????????}??
  25. ????????};??
  26. ????}??
  27. }??
  28. ??
  29. ??
  30. ??{??
  31. ????
  32. ??
  33. ????RunnableFuture?newTask();??
  34. }??
?

?7.2 ?停止基于线程的服务

应用程序通常会创建拥有多个线程的服务,如果应用程序准备退出,那么这些服务所拥有的线程也需要正确的结束,由于java没有抢占式方法停止线程,因此需要它们自行结束。

正确的封装原则是:除非拥有某个线程,否则不要对该线程进行操控,例如中断线程或者修改优先级等。

线程有个相应的所有者,即创建该线程的类,因此线程池是其工作者线程的所有者,如果要中断线程,那么应该使用线程池去中断。

线程的所有权是不可传递的。服务应该提供生命周期方法Lifecycle Method来关闭它自己以及它所拥有的线程。这样当应用程序关闭该服务的时候,服务就可以关闭所有的线程了。在ExecutorService中提供了shutdown和shutdownNow等方法,同样,在其他拥有线程的服务方法中也应该提供类似的关闭机制。

Tips:对于持有线程的服务,只要服务的存在时间大于创建线程的方法的存在时间,那么就应该提供生命周期方法。

7.2.1 ?示例:日志服务

我们通常会在应用程序中加入log信息,一般用的框架就是log4j。但是这种内联的日志功能会给一些高容量Highvolume应用程序带来一定的性能开销。另外一种替代方法是通过调用log方法将日志消息放入某个队列中,并由其他线程来处理。

  1. ?????queue;??
  2. ????
  3. ????
  4. ????
  5. ????
  6. ????
  7. ????
  8. ??
  9. ????
  10. ????????();??
  11. ????????
  12. ????????
  13. ????}??
  14. ??
  15. ????
  16. ????????loggerThread.start();??
  17. ????}??
  18. ??
  19. ????
  20. ????????
  21. ????????????isShutdown?=?
  22. ????????}??
  23. ????????loggerThread.interrupt();??
  24. ????}??
  25. ??
  26. ????
  27. ????????
  28. ????????????
  29. ????????????????
  30. ????????????++reservations;??
  31. ????????}??
  32. ????????queue.put(msg);??
  33. ????}??
  34. ??
  35. ????
  36. ????????
  37. ????????????
  38. ????????????????
  39. ????????????????????
  40. ????????????????????????
  41. ????????????????????????????
  42. ????????????????????????????????
  43. ????????????????????????}??
  44. ????????????????????????String?msg?=?queue.take();??
  45. ????????????????????????
  46. ????????????????????????????--reservations;??
  47. ????????????????????????}??
  48. ????????????????????????writer.println(msg);??
  49. ????????????????????}?
  50. ????????????????????}??
  51. ????????????????}??
  52. ????????????}?
  53. ????????????????writer.close();??
  54. ????????????}??
  55. ????????}??
  56. ????}??
  57. }??

7.2.2 ?通过ExecutorService去关闭

简单的程序可以直接在main函数中启动和关闭全局的ExecutorService,而在复杂程序中,通常会将ExecutorService封装在某个更高级别的服务中,并且该服务提供自己的生命周期方法。下面我们利用ExecutorService重构上面的日志服务:

  1. ????
  2. ????????
  3. ????????????exec.shutdown();?exec.awaitTermination(TIMEOUT,?UNIT);??
  4. ????????}??
  5. ????}??
  6. }??

7.2.3 ?利用Poison Pill对象关闭Producer-Consumer服务

7.2.5 ?当关闭线程池的时候,保存尚未开始的和开始后取消的任务数据,以备后面重新处理,下面是一个网页爬虫程序,关闭爬虫服务的时候将记录所有尚未开始的和已经取消的所有页面URL:

  1. ????
  2. ????
  3. ?????urlsToCrawl?=?();??
  4. ??
  5. ?????seen?=?();??
  6. ????
  7. ????
  8. ??
  9. ????
  10. ????????urlsToCrawl.add(startUrl);??
  11. ????}??
  12. ??
  13. ????
  14. ????????exec?=?
  15. ????????
  16. ????????urlsToCrawl.clear();??
  17. ????}??
  18. ??
  19. ????
  20. ????????
  21. ????????????saveUncrawled(exec.shutdownNow());??
  22. ????????????
  23. ????????????????saveUncrawled(exec.getCancelledTasks());??
  24. ????????}?
  25. ????????????exec?=?
  26. ????????}??
  27. ????}??
  28. ??
  29. ?????processPage(URL?url);??
  30. ??
  31. ?????uncrawled)?{??
  32. ????????
  33. ????????????urlsToCrawl.add(((CrawlTask)?task).getPage());??
  34. ????}??
  35. ??
  36. ????
  37. ????????exec.execute(
  38. ????}??
  39. ??
  40. ????
  41. ????????
  42. ??
  43. ????????CrawlTask(URL?url)?{??
  44. ????????????
  45. ????????}??
  46. ??
  47. ????????
  48. ??
  49. ????????
  50. ????????????
  51. ????????}??
  52. ??
  53. ????????
  54. ????????????seen.remove(url);??
  55. ????????????System.out.printf(
  56. ????????}??
  57. ??
  58. ????????
  59. ????????????
  60. ????????????????
  61. ????????????????????
  62. ????????????????submitCrawlTask(link);??
  63. ????????????}??
  64. ????????}??
  65. ??
  66. ????????
  67. ????????????
  68. ????????}??
  69. ????}??
  70. }??

<div id="" class="dp-highlighter">
<div class="bar">
<div class="tools">Java代码??<a title="收藏这段代码">

<img class="star" src="http://yidao620c.iteye.com/images/icon_star.png" alt="收藏代码">

  1. ????
  2. ?????tasksCancelledAtShutdown?=??
  3. ????????????Collections.synchronizedSet(());??
  4. ??
  5. ????
  6. ????????
  7. ????}??
  8. ??
  9. ????
  10. ????????exec.shutdown();??
  11. ????}??
  12. ??
  13. ?????shutdownNow()?{??
  14. ????????
  15. ????}??
  16. ??
  17. ????
  18. ????????
  19. ????}??
  20. ??
  21. ????
  22. ????????
  23. ????}??
  24. ??
  25. ????
  26. ????????????
  27. ????????
  28. ????}??
  29. ??
  30. ?????getCancelledTasks()?{??
  31. ????????
  32. ????????????
  33. ????????(tasksCancelledAtShutdown);??
  34. ????}??
  35. ??
  36. ????
  37. ????????exec.execute(
  38. ????????????
  39. ????????????????
  40. ????????????????????runnable.run();??
  41. ????????????????}?
  42. ????????????????????
  43. ????????????????????????????&&?Thread.currentThread().isInterrupted())??
  44. ????????????????????????tasksCancelledAtShutdown.add(runnable);??
  45. ????????????????}??
  46. ????????????}??
  47. ????????});??
  48. ????}??
  49. }??

7.3 ?处理非正常的线程终止

通过给应用程序提供一个UncaughtExceptionHandler异常处理器来处理未捕获的异常:

  1. ????
  2. ????????Logger?logger?=?Logger.getAnonymousLogger();??
  3. ????????logger.log(Level.SEVERE,?
  4. ????}??
  5. }??

?只有通过execute提交的任务,才能将它抛出的异常交给未捕获异常处理器。而通过submit提交的任务,无论是抛出未检查异常还是已检查异常,都将被认为是任务返回状态的一部分

7.4 ?JVM关闭的时候提供关闭钩子

在正常关闭JVM的时候,JVM首先调用所有已注册的关闭钩子Shutdown Hook。关闭钩子可以用来实现服务或者应用程序的清理工作,例如删除临时文件,或者清除无法由操作系统自动清除的资源。

最佳实践是对所有服务都使用同一个关闭钩子,并且在该关闭钩子中执行一系列的关闭操作。这确保了关闭操作在单个线程中串行执行,从而避免竞态条件的发生或者死锁问题。

    Runtime.getRuntime().addShutdownHook(
  1. ????
  2. ????????
  3. ????}??
  4. })??

总结:在任务、线程、服务以及应用程序等模块中的生命周期结束问题,可能会增加它们在设计和实现的时候的复杂性。我们通过利用FutureTask和Executor框架,可以帮助我们构建可取消的任务和服务。

原文地址:http://yidao620c.iteye.com/blog/1856914

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读