挂起和恢复线程

    Thread 的API中包含两个被淘汰的方法,它们用于临时挂起和重启某个线程,这些方法已经被淘汰,因为它们是不安全的,不稳定的。如果在不合适的时候挂起线程(比如,锁定共享资源时),此时便可能会发生死锁条件——其他线程在等待该线程释放锁,但该线程却被挂起了,便会发生死锁。另外,在长时间计算期间挂起线程也可能导致问题。

下面的代码演示了通过休眠来延缓运行,模拟长时间运行的情况,使线程更可能在不适当的时候被挂起:

  1. public class DeprecatedSuspendResume extends Object implements Runnable{
  2. //volatile关键字,表示该变量可能在被一个线程使用的同时,被另一个线程修改
  3. private volatile int firstVal;
  4. private volatile int secondVal;
  5. //判断二者是否相等
  6. public boolean areValuesEqual(){
  7. return ( firstVal == secondVal);
  8. }
  9. public void run() {
  10. try{
  11. firstVal = 0;
  12. secondVal = 0;
  13. workMethod();
  14. }catch(InterruptedException x){
  15. System.out.println("interrupted while in workMethod()");
  16. }
  17. }
  18. private void workMethod() throws InterruptedException {
  19. int val = 1;
  20. while (true){
  21. stepOne(val);
  22. stepTwo(val);
  23. val++;
  24. Thread.sleep(200);  //再次循环钱休眠200毫秒
  25. }
  26. }
  27. //赋值后,休眠300毫秒,从而使线程有机会在stepOne操作和stepTwo操作之间被挂起
  28. private void stepOne(int newVal) throws InterruptedException{
  29. firstVal = newVal;
  30. Thread.sleep(300);  //模拟长时间运行的情况
  31. }
  32. private void stepTwo(int newVal){
  33. secondVal = newVal;
  34. }
  35. public static void main(String[] args){
  36. DeprecatedSuspendResume dsr = new DeprecatedSuspendResume();
  37. Thread t = new Thread(dsr);
  38. t.start();
  39. //休眠1秒,让其他线程有机会获得执行
  40. try {
  41. Thread.sleep(1000);}
  42. catch(InterruptedException x){}
  43. for (int i = 0; i < 10; i++){
  44. //挂起线程
  45. t.suspend();
  46. System.out.println("dsr.areValuesEqual()=" + dsr.areValuesEqual());
  47. //恢复线程
  48. t.resume();
  49. try{
  50. //线程随机休眠0~2秒
  51. Thread.sleep((long)(Math.random()*2000.0));
  52. }catch(InterruptedException x){
  53. //略
  54. }
  55. }
  56. System.exit(0); //中断应用程序
  57. }
  58. }

某次运行结果如下:

从areValuesEqual()返回的值有时为true,有时为false。以上代码中,在设置firstVal之后,但在设置secondVal之前,挂起新线程会产生麻烦,此时输出的结果会为false(情况1),这段时间不适宜挂起线程,但因为线程不能控制何时调用它的suspend方法,所以这种情况是不可避免的。

当然,即使线程不被挂起(注释掉挂起和恢复线程的两行代码),如果在main线程中执行asr.areValuesEqual()进行比较时,恰逢stepOne操作执行完,而stepTwo操作还没执行,那么得到的结果同样可能是false(情况2)。

下面我们给出不用上述两个方法来实现线程挂起和恢复的策略——设置标志位。通过该方法实现线程的挂起和恢复有一个很好的地方,就是可以在线程的指定位置实现线程的挂起和恢复,而不用担心其不确定性。

对于上述代码的改进代码如下:

  1. public class AlternateSuspendResume extends Object implements Runnable {
  2. private volatile int firstVal;
  3. private volatile int secondVal;
  4. //增加标志位,用来实现线程的挂起和恢复
  5. private volatile boolean suspended;
  6. public boolean areValuesEqual() {
  7. return ( firstVal == secondVal );
  8. }
  9. public void run() {
  10. try {
  11. suspended = false;
  12. firstVal = 0;
  13. secondVal = 0;
  14. workMethod();
  15. } catch ( InterruptedException x ) {
  16. System.out.println("interrupted while in workMethod()");
  17. }
  18. }
  19. private void workMethod() throws InterruptedException {
  20. int val = 1;
  21. while ( true ) {
  22. //仅当贤臣挂起时,才运行这行代码
  23. waitWhileSuspended();
  24. stepOne(val);
  25. stepTwo(val);
  26. val++;
  27. //仅当线程挂起时,才运行这行代码
  28. waitWhileSuspended();
  29. Thread.sleep(200);
  30. }
  31. }
  32. private void stepOne(int newVal)
  33. throws InterruptedException {
  34. firstVal = newVal;
  35. Thread.sleep(300);
  36. }
  37. private void stepTwo(int newVal) {
  38. secondVal = newVal;
  39. }
  40. public void suspendRequest() {
  41. suspended = true;
  42. }
  43. public void resumeRequest() {
  44. suspended = false;
  45. }
  46. private void waitWhileSuspended()
  47. throws InterruptedException {
  48. //这是一个“繁忙等待”技术的示例。
  49. //它是非等待条件改变的最佳途径,因为它会不断请求处理器周期地执行检查,
  50. //更佳的技术是:使用Java的内置“通知-等待”机制
  51. while ( suspended ) {
  52. Thread.sleep(200);
  53. }
  54. }
  55. public static void main(String[] args) {
  56. AlternateSuspendResume asr =
  57. new AlternateSuspendResume();
  58. Thread t = new Thread(asr);
  59. t.start();
  60. //休眠1秒,让其他线程有机会获得执行
  61. try { Thread.sleep(1000); }
  62. catch ( InterruptedException x ) { }
  63. for ( int i = 0; i < 10; i++ ) {
  64. asr.suspendRequest();
  65. //让线程有机会注意到挂起请求
  66. //注意:这里休眠时间一定要大于
  67. //stepOne操作对firstVal赋值后的休眠时间,即300ms,
  68. //目的是为了防止在执行asr.areValuesEqual()进行比较时,
  69. //恰逢stepOne操作执行完,而stepTwo操作还没执行
  70. try { Thread.sleep(350); }
  71. catch ( InterruptedException x ) { }
  72. System.out.println("dsr.areValuesEqual()=" +
  73. asr.areValuesEqual());
  74. asr.resumeRequest();
  75. try {
  76. //线程随机休眠0~2秒
  77. Thread.sleep(
  78. ( long ) (Math.random() * 2000.0) );
  79. } catch ( InterruptedException x ) {
  80. //略
  81. }
  82. }
  83. System.exit(0); //退出应用程序
  84. }
  85. }

运行结果如下:

线程挂起的位置不确定main线程中执行asr.areValuesEqual()进行比较时,恰逢stepOne操作执行完,而stepTwo操作还没执行)asr.areValuesEqual()操作前,让main线程休眠450ms(>300ms),如果挂起请求发出时,新线程正执行到或即将执行到stepOne操作(如果在其前面的话,就会响应挂起请求,从而挂起线程),那么在stepTwo操作执行前,main线程的休眠还没结束,从而main线程休眠结束后执行asr.areValuesEqual()操作进行比较时,stepTwo操作已经执行完,因此也不会出现输出结果为false的情况。

可以将ars.suspendRequest()代码后的sleep代码去掉,或将休眠时间改为200(明显小于300即可)后,查看执行结果,会发现结果中依然会有出现false的情况。如下图所示:

总结:线程的挂起和恢复实现的正确方法是:通过设置标志位,让线程在安全的位置挂起

终止线程

终止线程的替代方法:同样是使用标志位,通过控制标志位来终止线程。

【Java并发编程】:线程挂起、恢复与终止的更多相关文章

  1. Java 并发编程 | 线程池详解

    原文: https://chenmingyu.top/concurrent-threadpool/ 线程池 线程池用来处理异步任务或者并发执行的任务 优点: 重复利用已创建的线程,减少创建和销毁线程造 ...

  2. java并发编程 线程基础

    java并发编程 线程基础 1. java中的多线程 java是天生多线程的,可以通过启动一个main方法,查看main方法启动的同时有多少线程同时启动 public class OnlyMain { ...

  3. java并发编程 | 线程详解

    个人网站:https://chenmingyu.top/concurrent-thread/ 进程与线程 进程:操作系统在运行一个程序的时候就会为其创建一个进程(比如一个java程序),进程是资源分配 ...

  4. Java并发编程:线程间通信wait、notify

    Java并发编程:线程间协作的两种方式:wait.notify.notifyAll和Condition 在前面我们将了很多关于同步的问题,然而在现实中,需要线程之间的协作.比如说最经典的生产者-消费者 ...

  5. Java并发编程:线程和进程的创建(转)

    Java并发编程:如何创建线程? 在前面一篇文章中已经讲述了在进程和线程的由来,今天就来讲一下在Java中如何创建线程,让线程去执行一个子任务.下面先讲述一下Java中的应用程序和进程相关的概念知识, ...

  6. Java并发编程——线程池的使用

    在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统 ...

  7. Java并发编程——线程池

    本文的目录大纲: 一.Java中的ThreadPoolExecutor类 二.深入剖析线程池实现原理 三.使用示例 四.如何合理配置线程池的大小 一.Java中的ThreadPoolExecutor类 ...

  8. Java并发编程-线程可见性&线程封闭&指令重排序

    一.指令重排序 例子如下: public class Visibility1 { public static boolean ready; public static int number; } pu ...

  9. Java并发编程--线程封闭(Ad-hoc封闭 栈封闭 ThreadLocal)

    线程封闭实现好的并发是一件困难的事情,所以很多时候我们都想躲避并发.避免并发最简单的方法就是线程封闭.什么是线程封闭呢?就是把对象封装到一个线程里,只有这一个线程能看到此对象.那么这个对象就算不是线程 ...

  10. JAVA 并发编程-线程范围内共享变量(五)

    线程范围内共享变量要实现的效果为: 多个对象间共享同一线程内的变量 watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsi ...

随机推荐

  1. HDU 3079 Vowel Counting (水题。。。判断元音)

    题意:n个字符串,如果元音就是输出大写,否则输出小写. 析:没啥可说的,只要判断AEIOU就OK了. 代码如下: #include <iostream> #include <cstd ...

  2. hdu1081 To The Max 2016-09-11 10:06 29人阅读 评论(0) 收藏

    To The Max Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total ...

  3. IDEA13 SVN配置

    这个算是解决了,idea13是支持svn 1.8. 步骤: 1.下载svn客户端软件,小乌龟:TortoiseSVN.安装的时候,一定要选择安装svn命令行的那个选项.当前版本1.8默认只会忽略命令行 ...

  4. Spring Boot 应用系列 1 -- Spring Boot 2 整合Spring Data JPA和Druid,双数据源

    最近Team开始尝试使用Spring Boot + Spring Data JPA作为数据层的解决方案,在网上逛了几圈之后发现大家并不待见JPA,理由是(1)MyBatis简单直观够用,(2)以Hib ...

  5. bash基本命令速查表

    来源:https://github.com/skywind3000/awesome-cheatsheets/blob/master/languages/bash.sh ################ ...

  6. js 日期去掉 -

    语法 stringObj.replace(rgExp, replaceText) replace 方法的语法包括下述部分: stringObj 必选项.要执行该替换的 String 对象或文字.该对象 ...

  7. C# GDI绘制仪表盘(纯代码实现)

    纯代码实现GDI绘制仪表盘,效果在代码下面. public partial class HalfDashboardUc : UserControl { /// <summary> /// ...

  8. RabbitMQ广播模式

    广播模式:1对多,produce发送一则消息多个consumer同时收到.注意:广播是实时的,produce只负责发出去,不会管对端是否收到,若发送的时刻没有对端接收,那消息就没了,因此在广播模式下设 ...

  9. POST与GET请求的区别

    https://www.cnblogs.com/logsharing/p/8448446.html 返回主页 在途中# 博客园首页新随笔联系订阅管理 随笔 - 33 文章 - 5 评论 - 40 GE ...

  10. python学习笔记6-集合

    # 集合是无序且不可重复的元素的集合 a = set([1,3,1,3,3,2,2,5]) a # {1, 2, 3, 5} b = set(range(2,5)) b # {2, 3, 4} # 1 ...