• 转: 【Java并发编程】之三:线程挂起、恢复与终止的正确方法(含代码)


    转载请注明出处:http://blog.csdn.net/ns_code/article/details/17095733


    挂起和恢复线程

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

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

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

        某次运行结果如下:

      

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

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


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

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

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

        运行结果如下:


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

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



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


     

    终止线程

       当调用Thread的start()方法,执行完run()方法后,或在run()方法中return,线程便会自然消亡。另外Thread API中包含了一个stop()方法,可以突然终止线程。但它在JDK1.2后便被淘汰了,因为它可能导致数据对象的崩溃。一个问题是,当线程终止时,很少有机会执行清理工作;另一个问题是,当在某个线程上调用stop()方法时,线程释放它当前持有的所有锁,持有这些锁必定有某种合适的理由——也许是阻止其他线程访问尚未处于一致性状态的数据,突然释放锁可能使某些对象中的数据处于不一致状态,而且不会出现数据可能崩溃的任何警告。

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

  • 相关阅读:
    ES基本介绍
    Mybatis 读写分离简单实现
    分享一个Flink checkpoint失败的问题和解决办法
    一次“内存泄露”引发的血案
    记一次堆外内存泄漏排查过程
    MySQL主从复制读写分离,看这篇就够了!
    JVM运行时内存数据区域
    .NET MVC 页面传值方式
    jQuery 对表格内容进行搜索筛选
    泛型
  • 原文地址:https://www.cnblogs.com/xuyatao/p/6917180.html
Copyright © 2020-2023  润新知