挂起和恢复线程

    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. C语言学生管理系统源码分享

    大家好 我就是如假包换的...陈玲 自从运营了C语言程序设计微信公众号 很多粉丝都给我备注 ...奇葩 实在是不敢当 也被人开始叫玲玲姐 我知道 很多人都想看我出境 我本人也有 年多的舞台演讲训练 实 ...

  2. (连通图 模板题)迷宫城堡--hdu--1269

    链接: http://acm.hdu.edu.cn/showproblem.php?pid=1269 http://acm.hust.edu.cn/vjudge/contest/view.action ...

  3. CGA填充算法之种子填充算法

    CGA填充算法之种子填充算法 平面区域填充算法是计算机图形学领域的一个很重要的算法,区域填充即给出一个区域的边界 (也可以是没有边界,只是给出指定颜色),要求将边界范围内的所有象素单元都修改成指定的颜 ...

  4. Elasticsearch 相关 api 操作

    A. es 操作 1. 检查 es 集群健康状态 2. 获取集群中的节点列表 3. 创建索引 4. 获取索引 5. 索引文档 6. 查询文档 7. 删除索引 8. 更新文档 9. 删除文档 10. 批 ...

  5. DevOps Workshop 研发运维一体化(成都站) 2016.05.08

    成都的培训与广州.上海.北京一样,只是会议室比较小,比较拥挤,大家都将就了.可惜换了电脑以后,没有找到培训时的照片了,遗憾. 培训思路基没有太大变化,基本按照下面的思路进行: 第一天对软件开发的需求管 ...

  6. C# 图像自动切换

    using System;using System.Collections.Generic;using System.ComponentModel;using System.Data;using Sy ...

  7. js form 表单 重置 清空

    清空 和 重置的差异是 清空是彻底清空input内容即便初始值value有值,重置是将input内容重置为value初始状态 很简单记录下 方便之后使用 //重置 //document.getElem ...

  8. The rapid development platform upgrade, leave the time to yourself, the work is lost to the soft platform

    Bring me back to your home. Please leave your work behind! Soft agile development framework V7.0 new ...

  9. 网易云 MySQL实例迁移的技术实现

    本文由  网易云 发布. 我们把数据库里部分或全部 Schema和数据迁移到另一个实例的行为称为实例迁移,将导出数据的实例称为源实例,导入数据的实例称为目标实例. 根据迁移数据库类型的不同,可以分为同 ...

  10. OVS 内核KEY值提取及匹配流表代码分析

    原文链接:http://ry0117.com/2016/12/24/OVS内核KEY值提取及匹配流表代码分析/ 当开启OVS后,创建datapath类型为system的网桥并他添加相关接口,OVS网桥 ...