关于Java中停止线程执行的方法总结

如何停止java的线程一直是开发多线程程序的一个非常头痛的问题,本文我们就来讨论一下关于Java中停止线程执行的方法,我们应该如何正确停止java中的线程。

Java中停止线程执行的方法

一、暂停或停止线程的理论


在Java编程中,要暂停或停止当前正在运行的线程,有几种方法。对于把线程转入睡眠Sleep状态,使用Thread.sleep()是最正确的方式。或许有人会问,为什么不使用等待wait()或通知notify()?要知道,使用等待或通知都不是很好的方式。
线程可以使用等待wait()实现被阻塞,这属于条件等待的方式,当条件满足后,又会从阻塞转为等待状态。尽管可以在等待wait()条件那里放一个超时设置,但等待wait()的设计目的不是这样的,等待wait()在设计上是用于Java线程间的通信。
而使用睡眠sleep()方式,可以让线程从当前开始睡眠指定的时间。注意不要使用睡眠sleep()方式去代替等待wait()或通知notify(),反之亦然。
Java线程通信的例子: http://javarevisited.blogspot.sg/2013/12/inter-thread-communication-in-java-wait-notify-example.html
等待wait()或通知notify()不应该用于暂停线程,还有一个原因,等待wait()或通知notify()需要一个锁。只能从一个同步的方法或同步的代码块去调用它们,获取锁和释放锁的开销是比较大的。而且,只是暂停线程的话,无需引入锁机制。
sleep()与wait()还有一点不同,sleep()会把当前的线程转入等待状态,它不会释放它持有的任何锁,而wait()使得线程转入阻塞状态,会释放掉自己持有的锁。
总之,Java多线程编程并不简单,即使是简单的任务,如创建线程、停止线程或暂停线程,都需要认真掌握Java API。

二、暂停或停止线程的实战

下面的例子中,要暂停线程,可以使用Thread.sleep()或TimeUnit.sleep()方法。例子中,有两个线程,主线程由JVM启动,它执行main()方法。第二个线程叫T1,它由主线程创建,用于循环运行游戏。我们传递的Runnable任务是一个无限循环,会一直运行直到我们停止它。注意看,我使用了volatile关键字。
主线程首先启动T1线程,再使用stop()方法停止线程。
在这个例子中,我们有两种方法停止线程的运行,使用Thread.sleep()方法或者是使用TimeUnit.sleep()方法。TimeUnit类既可以指定秒即TimeUnit.SECONDS,又可以指定毫秒即TimeUnit.MILLISECONDS。总的来说,使用TimeUnit的sleep()方法,使得代码更为易读。

点击(此处)折叠或打开

import static java.lang.Thread.currentThread;
import java.util.concurrent.TimeUnit;
public class ThreadPauseDemo{
    public static void main(String args[]) throws InterruptedException {
        Game game = new Game();
        Thread t1 = new Thread(game, "T1");
        t1.start();
        // 现在停止Game线程
        System.out.println(currentThread().getName() + " is stopping game thread");
        game.stop();
        // 查看Game线程停止的状态
        TimeUnit.MILLISECONDS.sleep(200);
        System.out.println(currentThread().getName() + " is finished now");
    }
}


点击(此处)折叠或打开

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class  Game  implements  Runnable{
     private  volatile  boolean  isStopped =  false ;
     public  void  run(){
         while (!isStopped){
             System.out.println( "Game thread is running......" );
             System.out.println( "Game thread is now going to pause" );
             try {
                 Thread.sleep( 200 );
             } <a href= "/tags.php/catch/"  target= "_blank" > catch </a>(InterruptedException e){
                 e.printStackTrace();
             }
             System.out.println( "Game thread is now resumed......" );
         }
         System.out.println( "Game thread is stopped......" );
     }
     public  void  stop(){
         isStopped =  true ;
     }
}


程序输出如下:
Game thread is running......
main is stopping game thread
Game thread is now going to pause
Game thread is now resumed......
Game thread is stopped......
main is finished now

注:
volatile关键字:当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取这个值时,它会去主内存中读取新值。volatile关键字保证了可见性。普通的共享变量不能保证可见性,因为普通共享变量被修改之后,什么时候被写入主存是不确定的,当其他线程去读取时,此时内存中可能还是原来的旧值,因此无法保证可见性。
通过synchronized和Lock也能够保证可见性,synchronized和Lock能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中。因此可以保证可见性。
一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么它就具备了两层语义:
1. 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
2. 禁止进行指令重排序。
volatile关键字禁止指令重排序有两层意思:
1. 当程序执行到volatile变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果已经对后面的操作可见;在其后面的操作肯定还没有进行。
2. 在进行指令优化时,不能将在对volatile变量访问的语句放在其后面执行,也不能把volatile变量后面的语句放到其前面执行。
volatile一般情况下不能代替sychronized,因为volatile不能保证操作的原子性,即使只是i++,实际上也是由多个原子操作组成:read i; inc; write i,假如多个线程同时执行i++,volatile只能保证他们操作的i是同一块内存,但依然可能出现写入脏数据的情况。

三、sleep()方法总结

Thread.sleep()方法可以让线程暂停或停止,它还有一些细节需要注意:
1. Thread.sleep()方法是一个静态方法,它总是可以让当前的线程转入睡眠。
2. 可以调用interrupt()方法把当前睡眠的线程唤醒。
3. sleep()方法不能保证线程能精准地在指定那一毫秒内转入睡眠,它的精度取决于系统的计时器。
4. 它不会释放它所获得的锁。




如何停止java线程

如何停止java的线程一直是一个困恼我们开发多线程程序的一个问题。这个问题最终在Java5的java.util.concurrent中得到了回答:使用interrupt(),让线程在run方法中停止。
简介

在Java的多线程编程中,java.lang.Thread类型包含了一些列的方法start(), stop(), stop(Throwable) and suspend(), destroy() and resume()。通过这些方法,我们可以对线程进行方便的操作,但是这些方法中,只有start()方法得到了保留。

在Sun公司的一篇文章《Why are Thread.stop, Thread.suspend and Thread.resume Deprecated? 》中详细讲解了舍弃这些方法的原因。那么,我们究竟应该如何停止线程呢?


建议使用的方法

在《Why are Thread.stop, Thread.suspend and Thread.resume Deprecated? 》中,建议使用如下的方法来停止线程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
     private  volatile  Thread blinker;
     public  void  stop() {
         blinker =  null ;
     }
     public  void  run() {
         Thread thisThread = Thread.currentThread();
         while  (blinker == thisThread) {
             try  {
                 thisThread.sleep(interval);
             catch  (InterruptedException e){
             }
             repaint();
         }
     }


关于使用volatile关键字的原因,请查看http://java.sun.com/docs/books/jls/second_edition/html/classes.doc.html#36930。
当线程处于非运行(Run)状态

当线程处于下面的状况时,属于非运行状态:

    当sleep方法被调用。

    当wait方法被调用。

    当被I/O阻塞,可能是文件或者网络等等。

当线程处于上述的状态时,使用前面介绍的方法就不可用了。这个时候,我们可以使用interrupt()来打破阻塞的情况,如:

1
2
3
4
5
6
7
public  void  stop() {
         Thread tmpBlinker = blinker;
         blinker =  null ;
         if  (tmpBlinker !=  null ) {
            tmpBlinker.interrupt();
         }
     }


当interrupt()被调用的时候,InterruptedException将被抛出,所以你可以再run方法中捕获这个异常,让线程安全退出:

1
2
3
4
5
6
try  {
    ....
    wait();
catch  (InterruptedException iex) {
    throw  new  RuntimeException( "Interrupted" ,iex);
}


阻塞的I/O

当线程被I/O阻塞的时候,调用interrupt()的情况是依赖与实际运行的平台的。在Solaris和Linux平台上将会抛出InterruptedIOException的异常,但是Windows上面不会有这种异常。所以,我们处理这种问题不能依靠于平台的实现。如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package  com.cnblogs.gpcuster
import  java.net.*;
import  java.io.*;
public  abstract  class  InterruptibleReader  extends  Thread {
     private  Object lock =  new  Object( );
     private  InputStream is;
     private  boolean  done;
     private  int  buflen;
     protected  void  processData( byte [] b,  int  n) { }
     class  ReaderClass  extends  Thread {
         public  void  run( ) {
             byte [] b =  new  byte [buflen];
             while  (!done) {
                 try  {
                     int  n = is.read(b,  0 , buflen);
                     processData(b, n);
                 catch  (IOException ioe) {
                     done =  true ;
                 }
             }
             synchronized (lock) {
                 lock.notify( );
             }
         }
     }
     public  InterruptibleReader(InputStream is) {
         this (is,  512 );
     }
     public  InterruptibleReader(InputStream is,  int  len) {
         this .is = is;
         buflen = len;
     }
     public  void  run( ) {
         ReaderClass rc =  new  ReaderClass( );
         synchronized (lock) {
             rc.start( );
             while  (!done) {
                 try  {
                     lock.wait( );
                 catch  (InterruptedException ie) {
                     done =  true ;
                     rc.interrupt( );
                     try  {
                         is.close( );
                     catch  (IOException ioe) {}
                 }
             }
         }
     }
}



另外,我们也可以使用InterruptibleChannel接口。 实现了InterruptibleChannel接口的类可以在阻塞的时候抛出ClosedByInterruptException。如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package  com.cnblogs.gpcuster
import  java.io.BufferedReader;
import  java.io.FileDescriptor;
import  java.io.FileInputStream;
import  java.io.InputStream;
import  java.io.InputStreamReader;
import  java.nio.channels.Channels;
public  class  InterruptInput {   
     static  BufferedReader in =  new  BufferedReader(
             new  InputStreamReader(
             Channels.newInputStream(
             ( new  FileInputStream(FileDescriptor.in)).getChannel())));
     
     public  static  void  main(String args[]) {
         try  {
             System.out.println( "Enter lines of input (user ctrl+Z Enter to terminate):" );
             System.out.println( "(Input thread will be interrupted in 10 sec.)" );
             // interrupt input in 10 sec
             ( new  TimeOut()).start();
             String line =  null ;
             while  ((line = in.readLine()) !=  null ) {
                 System.out.println( "Read line:'" +line+ "'" );
             }
         catch  (Exception ex) {
             System.out.println(ex.toString());  // printStackTrace();
         }
     }
     
     public  static  class  TimeOut  extends  Thread {
         int  sleepTime =  10000 ;
         Thread threadToInterrupt =  null ;    
         public  TimeOut() {
             // interrupt thread that creates this TimeOut.
             threadToInterrupt = Thread.currentThread();
             setDaemon( true );
         }
         
         public  void  run() {
             try  {
                 sleep( 10000 );  // wait 10 sec
             catch (InterruptedException ex) { /*ignore*/ }
             threadToInterrupt.interrupt();
         }
     }
}


这里还需要注意一点,当线程处于写文件的状态时,调用interrupt()不会中断线程。



如何正确停止java中的线程 

为什么不能使用Thread.stop()方法?

从SUN的官方文档可以得知,调用Thread.stop()方法是不安全的,这是因为当调用Thread.stop()方法时,会发生下面两件事:

1. 即刻抛出ThreadDeath异常,在线程的run()方法内,任何一点都有可能抛出ThreadDeath Error,包括在catch或finally语句中。
2. 释放该线程所持有的所有的锁


当线程抛出ThreadDeath异常时,会导致该线程的run()方法突然返回来达到停止该线程的目的。ThreadDetath异常可以在该线程run()方法的任意一个执行点抛出。但是,线程的stop()方法一经调用线程的run()方法就会即刻返回吗?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
public  static  void  main(String[] args) {   
         try  {   
             Thread t =  new  Thread() {   
                 public  synchronized  void  run() {   
                     try  {   
                         long  start=System.currentTimeMillis();   
                         for  ( int  i =  0 ; i <  100000 ; i++)   
                             System.out.println( "runing.."  + i);   
                         System.out.println((System.currentTimeMillis()-start)/ 1000 );   
                     catch  (Throwable ex) {   
                         System.out.println( "Caught in run: "  + ex);   
                         ex.printStackTrace();   
                     }   
                 }   
             };   
             t.start();   
             // Give t time to get going...   
             Thread.sleep( 100 );   
             t.stop();  // EXPECT COMPILER WARNING   
         catch  (Throwable t) {   
             System.out.println( "Caught in main: "  + t);   
             t.printStackTrace();   
         }   
   
     }  
public  static  void  main(String[] args) {  
         try  {  
             Thread t =  new  Thread() {  
                 public  synchronized  void  run() {  
                     try  {  
                         long  start=System.currentTimeMillis();  
                         for  ( int  i =  0 ; i <  100000 ; i++)  
                             System.out.println( "runing.."  + i);  
                         System.out.println((System.currentTimeMillis()-start)/ 1000 );  
                     catch  (Throwable ex) {  
                         System.out.println( "Caught in run: "  + ex);  
                         ex.printStackTrace();  
                     }  
                 }  
             };  
             t.start();  
             // Give t time to get going...  
             Thread.sleep( 100 );  
             t.stop();  // EXPECT COMPILER WARNING  
         catch  (Throwable t) {  
             System.out.println( "Caught in main: "  + t);  
             t.printStackTrace();  
         }  
   
     }


假设我们有如上一个工作线程,它的工作是数数,从1到1000000,我们的目标是在它进行数数的过程中,停止该线程的运作。如果我们按照上面的方式来调用thread.stop()方法,原则上是可以实现我们的目标的,根据SUN官方文档的解释,加上在上面的程序中,主线程只休眠了100ms,而工作线程从1数到1000000所花时间大概是4-5s,那么该工作线程应该只从1数到某个值(小于1000000),然后线程停止。 

但是根据运行结果来看,并非如此。

结果:

。。。

runing..99998
runing..99999

5

。。。

runing..99998
runing..99999
4

每次运行的结果都表明,工作线程并没有停止,而是每次都成功的数完数,然后正常中止,而不是由stop()方法进行终止的。这个是为什么呢?根据SUN的文档,原则上只要一调用thread.stop()方法,那么线程就会立即停止,并抛出ThreadDeath error,查看了Thread的源代码后才发现,原先Thread.stop0()方法是同步的,而我们工作线程的run()方法也是同步,那么这样会导致主线程和工作线程共同争用同一个锁(工作线程对象本身),由于工作线程在启动后就先获得了锁,所以无论如何,当主线程在调用t.stop()时,它必须要等到工作线程的run()方法执行结束后才能进行,结果导致了上述奇怪的现象。

把上述工作线程的run()方法的同步去掉,再进行执行,结果就如上述第一点描述的那样了

可能的结果:

runing..4149
runing..4150
runing..4151
runing..4152runing..4152Caught in run: java.lang.ThreadDeath

或者

runing..5245
runing..5246
runing..5247
runing..5248runing..5248Caught in run: java.lang.ThreadDeath


接下来是看看当调用thread.stop()时,被停止的线程会不会释放其所持有的锁,看如下代码:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
public  static  void  main(String[] args) {   
         final  Object lock =  new  Object();   
         try  {   
             Thread t0 =  new  Thread() {   
                 public  void  run() {   
                     try  {   
                         synchronized  (lock) {   
                             System.out.println( "thread->"  + getName()   
                                     " acquire lock." );   
                             sleep( 3000 ); // sleep for 3s   
                             System.out.println( "thread->"  + getName()   
                                     " release lock." );   
                         }   
                     catch  (Throwable ex) {   
                         System.out.println( "Caught in run: "  + ex);   
                         ex.printStackTrace();   
                     }   
                 }   
             };   
   
             Thread t1 =  new  Thread() {   
                 public  void  run() {   
                     synchronized  (lock) {   
                         System.out.println( "thread->"  + getName()   
                                 " acquire lock." );   
                     }   
                 }   
             };   
   
             t0.start();   
             // Give t time to get going...   
             Thread.sleep( 100 );   
             //t0.stop();   
             t1.start();   
         catch  (Throwable t) {   
             System.out.println( "Caught in main: "  + t);   
             t.printStackTrace();   
         }   
   
     }  
         
public  static  void  main(String[] args) {  
     final  Object lock =  new  Object();  
     try  {  
         Thread t0 =  new  Thread() {  
             public  void  run() {  
                 try  {  
                     synchronized  (lock) {  
                         System.out.println( "thread->"  + getName()  
                                 " acquire lock." );  
                         sleep( 3000 ); // sleep for 3s  
                         System.out.println( "thread->"  + getName()  
                                 " release lock." );  
                     }  
                 catch  (Throwable ex) {  
                     System.out.println( "Caught in run: "  + ex);  
                     ex.printStackTrace();  
                 }  
             }  
         };  
         Thread t1 =  new  Thread() {  
             public  void  run() {  
                 synchronized  (lock) {  
                     System.out.println( "thread->"  + getName()  
                             " acquire lock." );  
                 }  
             }  
         };  
         t0.start();  
         // Give t time to get going...  
         Thread.sleep( 100 );  
         //t0.stop();  
         t1.start();  
     catch  (Throwable t) {  
         System.out.println( "Caught in main: "  + t);  
         t.printStackTrace();  
     }  
}



当没有进行t0.stop()方法的调用时, 可以发现,两个线程争用锁的顺序是固定的。

输出:

thread->Thread-0 acquire lock.
thread->Thread-0 release lock.
thread->Thread-1 acquire lock.


但调用了t0.stop()方法后,(去掉上面的注释//t0.stop();),可以发现,t0线程抛出了ThreadDeath error并且t0线程释放了它所占有的锁。


输出:

thread->Thread-0 acquire lock.
thread->Thread-1 acquire lock.
Caught in run: java.lang.ThreadDeath
java.lang.ThreadDeath
 at java.lang.Thread.stop(Thread.java:715)
 at com.yezi.test.timeout.ThreadStopTest.main(ThreadStopTest.java:40)

从上面的程序验证结果来看,thread.stop()确实是不安全的。它的不安全主要是针对于第二点:释放该线程所持有的所有的锁。一般任何进行加锁的代码块,都是为了保护数据的一致性,如果在调用thread.stop()后导致了该线程所持有的所有锁的突然释放,那么被保护数据就有可能呈现不一致性,其他线程在使用这些被破坏的数据时,有可能导致一些很奇怪的应用程序错误。


如何正确停止线程

关于如何正确停止线程,这篇文章(how to stop thread)给出了一个很好的答案, 总结起来就下面3点(在停止线程时):

1. 使用violate boolean变量来标识线程是否停止
2. 停止线程时,需要调用停止线程的interrupt()方法,因为线程有可能在wait()或sleep(), 提高停止线程的即时性
3. 对于blocking IO的处理,尽量使用InterruptibleChannel来代替blocking IO
 

核心如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
If you are writing your own small thread then you should follow the following example code.
private  volatile  Thread myThread;
public  void  stopMyThread() {
     Thread tmpThread = myThread;
     myThread =  null ;
     if  (tmpThread !=  null ) {
         tmpThread.interrupt();
     }
}
public  void  run() {
     if  (myThread ==  null ) {
        return // stopped before started.
     }
     try  {
         // all the run() method's code goes here
         ...
         // do some work
         Thread.yield();  // let another thread have some time perha<a href="/fw/photo.html" target="_blank">ps</a> to stop this one.
         if  (Thread.currentThread().isInterrupted()) {
            throw  new  InterruptedException( "Stopped by ifInterruptedStop()" );
         }
         // do some more work
         ...
     catch  (Throwable t) {
        // log/handle all errors here
     }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值