线程错误终止之destroy与stop方法
记得以前初学Java的时候,由于缺少对锁、同步、异步等这些线程的知识,想当然的以为destroy与stop方法都能正确的停止Java线程的执行。但是,后来随着工作的积累,以及对线程安全的一些理解,慢慢认识到这两个方法是有问题的,并且这两方法也早已在java doc上被指名是弃用的。
destroy()这个方法其实根本没干什么事情,只是抛出了一个NoSuchMethodError,所以说该方法无法终止线程,因此不能望文生意:
/** * Throws {@link NoSuchMethodError}. * * @deprecated This method was originally designed to destroy this * thread without any cleanup. Any monitors it held would have * remained locked. However, the method was never implemented. * If if were to be implemented, it would be deadlock-prone in * much the manner of {@link #suspend}. If the target thread held * a lock protecting a critical system resource when it was * destroyed, no thread could ever access this resource again. * If another thread ever attempted to lock this resource, deadlock * would result. Such deadlocks typically manifest themselves as * "frozen" processes. For more information, see * <a href="{@docRoot}/../technotes/guides/concurrency/threadPrimitiveDeprecation.html"> * Why are Thread.stop, Thread.suspend and Thread.resume Deprecated?</a>. * @throws NoSuchMethodError always */ @Deprecated public void destroy() { throw new NoSuchMethodError(); }
对于stop方法,其本质就是因为会线程不安全,它会直接终止run方法的调用,并且会抛出一个ThreadDeath错误,如果线程持有某个对象锁的话,会完全释放锁,导致对象状态不一致。具体细节可以看官方的java
doc;
Deprecated. This method is inherently unsafe. Stopping a thread with Thread.stop causes it to unlock all of the monitors that it has locked (as a natural consequence of the unchecked ThreadDeath exception propagating up the stack). If any of the objects previously protected by these monitors were in an inconsistent state, the damaged objects become visible to other threads, potentially resulting in arbitrary behavior. Many uses of stop should be replaced by code that simply modifies some variable to indicate that the target thread should stop running. The target thread should check this variable regularly, and return from its run method in an orderly fashion if the variable indicates that it is to stop running. If the target thread waits for long periods (on a condition variable, for example), the interrupt method should be used to interrupt the wait. For more information, see <a target=_blank href="http://docs.oracle.com/javase/6/docs/technotes/guides/concurrency/threadPrimitiveDeprecation.html">Why are Thread.stop, Thread.suspend and Thread.resume Deprecated?</a>.
线程的正确终止
在上述的destroy和stop方法都一一被否定之后,那还有什么方式能够正确多终止线程呢?总的来说,在java中有两种解决方案:
- 标志,在run方法中通过一个标记来进行结束,由于该方式很寻常就不做举例
- interrupt,通过异常中断
下面就主要针对interrupt进行展开探讨。
/** * Interrupts this thread. * * <p> Unless the current thread is interrupting itself, which is * always permitted, the {@link #checkAccess() checkAccess} method * of this thread is invoked, which may cause a {@link * SecurityException} to be thrown. * * <p> If this thread is blocked in an invocation of the {@link * Object#wait() wait()}, {@link Object#wait(long) wait(long)}, or {@link * Object#wait(long, int) wait(long, int)} methods of the {@link Object} * class, or of the {@link #join()}, {@link #join(long)}, {@link * #join(long, int)}, {@link #sleep(long)}, or {@link #sleep(long, int)}, * methods of this class, then its interrupt status will be cleared and it * will receive an {@link InterruptedException}. * * <p> If this thread is blocked in an I/O operation upon an {@link * java.nio.channels.InterruptibleChannel </code>interruptible * channel<code>} then the channel will be closed, the thread's interrupt * status will be set, and the thread will receive a {@link * java.nio.channels.ClosedByInterruptException}. * * <p> If this thread is blocked in a {@link java.nio.channels.Selector} * then the thread's interrupt status will be set and it will return * immediately from the selection operation, possibly with a non-zero * value, just as if the selector's {@link * java.nio.channels.Selector#wakeup wakeup} method were invoked. * * <p> If none of the previous conditions hold then this thread's interrupt * status will be set. </p> * * <p> Interrupting a thread that is not alive need not have any effect. * * @throws SecurityException * if the current thread cannot modify this thread * * @revised 6.0 * @spec JSR-51 */ public void interrupt() { if (this != Thread.currentThread()) checkAccess(); synchronized (blockerLock) { Interruptible b = blocker; if (b != null) { interrupt0(); // Just to set the interrupt flag b.interrupt(this); return; } } interrupt0(); }
根据java源码摘出的interrupt方法注释和方法体的实现,可以归纳为:通过调用interrupt方法可以使得处于阻塞状态的线程抛出一个异常,即interrupt方法可以用来中断一个正处于阻塞状态的线程;另外,改方法还会设置线程的中断状态(注:isInterrupted()可以用来查询中断状态)。
实践检验真理
实验interrupt能否中断处于非阻塞状态的线程
代码:
public class ThreadTest { /** * @param args */ public static void main(String[] args) { WorkThread wThread = new WorkThread(); wThread.start(); System.out.println("开始调用wThread.interrupt()"); wThread.interrupt(); System.out.println("结束调用wThread.interrupt()"); } } class WorkThread extends Thread { public void run() { for (int i = 0; i < Byte.MAX_VALUE;) { System.out.println("工作线程运行" + (++i)); } } }
运行结果:
实际是打印到127结束,由于太长,只截取部分。从中可以发现,for循环会一直运行直到变量i的值超出Byte.MAX_VALUE。
结论:interrupt无法中断非阻塞状态的线程。
但是,我门可以换个思路,既然之前提过interrupt方法会设置线程的中断状态,那么我门可以通过isInterrupt()来进行判断,从而中断线程。(本质上这种方案时标志中断)
上代码,只对WorkThread的条件循环增加一个标志判断——isInterrupt():
class WorkThread extends Thread { public void run() { for (int i = 0; i < Byte.MAX_VALUE && isInterrupted();) { System.out.println("工作线程运行" + (++i)); } } }
运行结果:
从结果上来看,interrupt配合isInterrupt()能够中断非阻塞状态的线程。注:本质还是标志中断
interrupt能否中断阻塞状态的线程
上代码:
public class ThreadTest { /** * @param args */ public static void main(String[] args) { WorkThread wThread = new WorkThread(); wThread.start(); System.out.println("开始调用wThread.interrupt()"); wThread.interrupt(); System.out.println("结束调用wThread.interrupt()"); } } class WorkThread extends Thread { public void run() { System.out.println("工作线程sleep"); for (int i = 0; i < Byte.MAX_VALUE;) { try { sleep(10 * 1000); System.out.println("工作线程运行"+(++i)); } catch (InterruptedException e) { System.out.println("工作线程InterruptedException"); break; } } System.out.println("工作线程运行完毕"); } }
运行结果:
从程序到运行结果来看,当工作线程进入sleep(即阻塞)的时候,调用interrupt方法,将会促使线程抛出异常。
结论:interrupt能够中断阻塞状态的线程。
总结
Java没有立即终止线程的机制,Java的Thread类提供的destroy和stop方法无法正确终止线程,只能通过标志或者interrup方法来进行。