线程的停止与中断
要点
- public void interrupt() 设置中断状态为true,阻塞线程会抛出InterruptedException
- public boolean isInterrupted() 仅获取中断状态
- public static boolean interrupted() 获取并重置中断状态为false
- private native boolean isInterrupted(boolean ClearInterrupted);
如何停止线程
-
正常停止
就是说线程在运行完run方法后直接正常退出。这种方式很明显,就不做案例演示了。 -
stop强制停止
stop()方法是一个过时的方法,也就是说已经不建议使用。这里也不做详细讨论 -
异常导致的线程停止
就是说在run方法运行期间产生了异常,导致了线程停止执行。看一下下面的案例,在run方法中是个无线循环。我们抛出了一个运行时异常就导致了线程退出。
public class TestExceptionOut implements Runnable {
public static void main(String[] args) {
Thread thread = new Thread(new TestExceptionOut());
thread.start();
}
@Override
public void run() {
while (true) {
throw new RuntimeException("线程异常退出");
}
}
}
- 中断
新启动的线程有一个中断的状态,默认为false。当我们调用isInterrupted或者interrupted方法可以获取到这个状态值。我们在run里面可以通过获取这个状态来优雅的结束线程。这两个方法都是Thread类的方法,先看一下这两个方法的源代码。
public boolean isInterrupted() {
return isInterrupted(false);
}
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
从上面的方法中我们可以看出两个方法都去调用了一个isInterrupted(boolean ClearInterrupted)方法,看下面的源代码我们发现这是一个私有的native方法。该方法的意思测试线程是否已经被中断,并返回中断的状态,同时根据传递过来的ClearInterrupted值判断是否需要重置中断状态,true表示重置,false表示不重置(重置是讲状态置为false)。因此我们可以看出isInterrupted()方法可以获得中断的状态但是不会重置线程的中断状态。interrupted()不只是会获得线程中断状态,同时也会将状态重置为false。
当然这两个方法还有个区别。一个非静态的方法,一个是静态方法。而且调用isInterrupted(boolean ClearInterrupted)的方式也不一样。一个是直接调用,一个是通过currentThread()获取了线程后再调用。
/**
* Tests if some Thread has been interrupted. The interrupted state
* is reset or not based on the value of ClearInterrupted that is
* passed.
*/
private native boolean isInterrupted(boolean ClearInterrupted);
我们先通过一个案例测试一下public boolean isInterrupted()方法。看下面的案例主线程启动之后先休眠2秒,然后在调用t.interrupt()方法更改线程的中断状态为true。我们会看到一开始子线程打印的是false,调用t.interrupt()之后打印的是true,且继续运行打印状态依然是true。再往下看一下测试interrupted()方法的案例。
public class TestIsInterrupted extends Thread {
public static void main(String[] args) {
TestIsInterrupted t = new TestIsInterrupted();
t.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t.interrupt();
}
@Override
public void run() {
while (true) {
System.out.println("子线程自己打印t 的状态 " + isInterrupted());
}
}
}
下面的案例中我们在run方法中做了一点小改动,就是判断状态为true的时候休眠一下以便可以看到打印的值。继续运行我们就会看到和isInterrupted()相反,打印的结果会变为false。这里为了测试方便上面的案例是集成Thread的因为isInterrupted()方法是非静态的,在run里面不继承Thread不能调用。既然验证了这两个方法的作用,那么我们在run里面就可以去获取这个状态来退出当前线程。下面我们在来详细看一下interrupt()方法。
public class TestInterrupted implements Runnable {
public static void main(String[] args) {
Thread thread = new Thread(new TestInterrupt());
thread.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread.interrupt();
System.out.println("已经调用interrupt");
}
@Override
public void run() {
while (true) {
boolean interrupted = Thread.interrupted();
System.out.println("子线程自己打印t 的状态 " + interrupted);
if (interrupted) {
// 为了看到打印的true线程中断状态
System.out.println("子线程自己打印t 的状态 " + interrupted);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
interrupt();方法在之前的案例中我们可以发现该方法可以把线程的中断状态改为true。我们看源代码,发现如果是一个线程调用其他线程的interrupt()方法会去检查权限问题,可能会抛出SecurityException异常。然后下面后调用native的interrupt0方法和sun.nio.ch.Interruptible.interrupt(Thread)方法。
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();
}
interrupt(); 还有一个重要的特性那就是如果线程是处于阻塞状态,即调用线程的wait(), wait(long)或wait(long, int),join(), join(long), join(long, int), sleep(long), sleep(long, int)会让线程进入阻塞状态。若线程在阻塞状态时,调用了它的interrupt()方法,那么它的“中断状态”会被清除并且会收到一个InterruptedException异常。看下面的例子我们用sleep方法演示一下。
public class TestInterrupt implements Runnable {
public static void main(String[] args) {
Thread t1 = new Thread(new TestInterrupt());
t1.start();
t1.interrupt();
}
@Override
public void run() {
try {
Thread.sleep(50000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}