最新下载
热门教程
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
关于Java中停止线程执行的方法总结
时间:2022-06-29 02:52:26 编辑:袖梨 来源:一聚教程网
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");
}
}
点击(此处)折叠或打开
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); } catch(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? 》中,建议使用如下的方法来停止线程:
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()来打破阻塞的情况,如:
public void stop() { Thread tmpBlinker = blinker; blinker = null; if (tmpBlinker != null) { tmpBlinker.interrupt(); } }
当interrupt()被调用的时候,InterruptedException将被抛出,所以你可以再run方法中捕获这个异常,让线程安全退出:
try { .... wait(); } catch (InterruptedException iex) { throw new RuntimeException("Interrupted",iex); }
阻塞的I/O
当线程被I/O阻塞的时候,调用interrupt()的情况是依赖与实际运行的平台的。在Solaris和Linux平台上将会抛出InterruptedIOException的异常,但是Windows上面不会有这种异常。所以,我们处理这种问题不能依靠于平台的实现。如:
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。如:
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()方法就会即刻返回吗?
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()时,被停止的线程会不会释放其所持有的锁,看如下代码:
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
核心如下:
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 perhaps 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 } }
相关文章
- 人们熟悉的寄居蟹属于以下哪种分类 神奇海洋11月21日答案 11-21
- 第五人格11.22共研服有什么更新 11月22日共研服更新内容介绍 11-21
- 原神恰斯卡怎么培养 11-21
- 无期迷途四星装束是谁 11-21
- 王者荣耀帝丹高中校服怎么获得 11-21
- 光遇姆明季后续版本怎么玩 11-21