在上篇文章《多线程的使用——Thread类和Runnable接口》中提到中断线程的问题。在JAVA中,曾经使用stop方法来中止线程,然而,该方法具备固有的不安全性,于是已经被抛弃(Deprecated)。那么应该怎么结束一个进程呢?官方文档中对此有详细说明:《为什么不同意使用 Thread.stop、Thread.suspend 和 Thread.resume?》。在此引用stop方法的说明:html
package chapter2; public class ThreadFlag extends Thread { public volatile boolean exit = false; public void run() { while (!exit); } public static void main(String[] args) throws Exception { ThreadFlag thread = new ThreadFlag(); thread.start(); sleep(5000); // 主线程延迟5秒 thread.exit = true; // 终止线程thread thread.join(); System.out.println("线程退出!"); } }
在上面代码中定义了一个退出标志exit,当exit为true时,while循环退出,exit的默认值为false.在定义exit时,使用了一个Java关键字volatile,这个关键字的目的是使exit同步,也就是说在同一时刻只能由一个线程来修改exit的值。java
package com.jvm.study.thread; public class TestThread implements Runnable{ boolean stop = false; public static void main(String[] args) throws Exception { Thread thread = new Thread(new TestThread(),"My Thread"); System.out.println( "Starting thread..." ); thread.start(); Thread.sleep( 3000 ); System.out.println( "Interrupting thread..." ); thread.interrupt(); System.out.println("线程是否中断:" + thread.isInterrupted()); Thread.sleep( 3000 ); System.out.println("Stopping application..." ); } public void run() { while(!stop){ System.out.println( "My Thread is running..." ); // 让该循环持续一段时间,使上面的话打印次数少点 long time = System.currentTimeMillis(); while((System.currentTimeMillis()-time < 1000)) { } } System.out.println("My Thread exiting under request..." ); } }
package com.jvm.study.thread; public class TestThread2 implements Runnable { boolean stop = false; public static void main(String[] args) throws Exception { Thread thread = new Thread(new TestThread2(), "My Thread2"); System.out.println("Starting thread..."); thread.start(); Thread.sleep(10000); System.out.println("Interrupting thread..."); thread.interrupt(); System.out.println("线程是否中断:" + thread.isInterrupted()); Thread.sleep(3000); System.out.println("Stopping application..."); Thread.sleep(30000); } public void run() { while (!stop) { System.out.println("My Thread is running..."); // 让该循环持续一段时间,使上面的话打印次数少点 long time = System.currentTimeMillis(); while ((System.currentTimeMillis() - time < 1000)) { } if (Thread.currentThread().isInterrupted()) { return; } } System.out.println("My Thread exiting under request..."); } }
注意:interrupted与isInterrupted方法的区别(见API文档)程序员
当线程处于下面的Blocked状态,经过调用interrupt()方法来中止线程:编程
2.一、当线程处于下面的情况时((1)若是线程在调用 Object 类的 wait()、wait(long) 或 wait(long, int) 方法,或者该类的 join()、join(long)、join(long, int)、sleep(long) 或 sleep(long, int) 方法过程当中受阻,则其中断状态将被清除,它还将收到一个InterruptedException异常。这个时候,咱们能够经过捕获InterruptedException异常来终止线程的执行,具体能够经过return等退出或改变共享变量的值使其退出。),属于非运行状态:api
当sleep方法被调用。缓存
当wait方法被调用。安全
当被I/O阻塞,多是文件或者网络等等。网络
当线程处于上述的状态时,使用前面介绍的方法就不可用了。这个时候,咱们可使用interrupt()来打破阻塞的状况,如:多线程
public void stop() { Thread tmpBlinker = blinker; blinker = null; if (tmpBlinker != null) { tmpBlinker.interrupt(); } }
当interrupt()被调用的时候,InterruptedException将被抛出,因此你能够再run方法中捕获这个异常,让线程安全退出:oracle
try { .... wait(); } catch (InterruptedException iex) { throw new RuntimeException("Interrupted",iex); }
当线程被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()不会中断线程。
3、不提倡的stop()方法
臭名昭著的stop()中止线程的方法已不提倡使用了,缘由是什么呢?
当在一个线程对象上调用stop()方法时,这个线程对象所运行的线程就会当即中止,并抛出特殊的ThreadDeath()异常。这里的“当即”由于太“当即”了,
在java多线程编程中,线程的终止能够说是一个必然会遇到的操做。可是这样一个常见的操做其实并非一个可以垂手可得实现的操做,并且在某些场景下状况会变得更复杂更棘手。
Java标准API中的Thread类提供了stop方法能够终止线程,可是很遗憾,这种方法不建议使用,缘由是这种方式终止线程中断临界区代码执行,并会释放线程以前获取的监控器锁,这样势必引发某些对象状态的不一致(由于临界区代码通常是原子的,不会被干扰的),具体缘由能够参考资料[1]。这样一来,就必须根据线程的特色使用不一样的替代方案以终止线程。根据中止线程时线程执行状态的不一样有以下中止线程的方法。
处于运行状态的线程就是常见的处于一个循环中不断执行业务流程的线程,这样的线程须要经过设置中止变量的方式,在每次循环开始处判断变量是否改变为中止,以达到中止线程的目的,好比以下代码框架:
private volatile Thread blinker; public void stop() { blinker = null; } public void run() { Thread thisThread = Thread.currentThread(); while (blinker == thisThread) { try { //业务流程 } catch (Exception e){} } }
若是主线程调用该线程对象的stop方法,blinker对象被设置为null,则线程的下次循环中blinker!=thisThread,于是能够退出循环,并退出run方法而使线程结束。将引用变量blinker的类型前加上volatile关键字的目的是防止编译器对该变量存取时的优化,这种优化主要是缓存对变量的修改,这将使其余线程不会马上看到修改后的blinker值,从而影响退出。此外,Java标准保证被volatile修饰的变量的读写都是原子的。
上述的Thread类型的blinker彻底能够由更为简单的boolean类型变量代替。
线程的非运行状态常见的有以下两种状况:
可中断等待:线程调用了sleep或wait方法,这些方法可抛出InterruptedException;
Io阻塞:线程调用了IO的read操做或者socket的accept操做,处于阻塞状态。
上面已经讲过。
处于大数据IO读写中的线程实际上处于运行状态,而不是等待或阻塞状态,所以上面的interrupt机制不适用。线程处于IO读写中能够当作是线程运行中的一种特例。中止这样的线程的办法是强行close掉io输入输出流对象,使其抛出异常,进而使线程中止。
最好的建议是将大数据的IO读写操做放在循环中进行,这样能够在每次循环中都有线程中止的时机,这也就将问题转化为如何中止正在运行中的线程的问题了。
有时,线程中的run方法须要足够健壮以支持在线程实际运行前终止线程的状况。即在Thread建立后,到Thread的start方法调用前这段时间,调用自定义的stop方法也要奏效。从上述的中止处于等待状态线程的代码示例中,stop方法并不能终止运行前的线程,由于在Thread的start方法被调用前,调用interrupt方法并不会将Thread对象的中断状态置位,这样当run方法执行时,currentThread的isInterrupted方法返回false,线程将继续执行下去。
为了解决这个问题,不得不本身再额外建立一个volatile标志量,并将其加入run方法的最开头:
public void run() { if (myThread == null) { return; // stopped before started. } while(!Thread.currentThread().isInterrupted()){ //业务逻辑 } }
还有一种解决方法,也能够在run中直接使用该自定义标志量,而不使用isInterrupted方法判断线程是否应该中止。这种方法的run代码框架实际上和中止运行时线程的同样。