线程并发系列文章:
Java 线程基础
Java “优雅”地中断线程
Java 线程状态
真正理解Java Volatile的妙用
Java ThreadLocal你之前了解的可能有误
Java Unsafe/CAS/LockSupport 应用与原理
Java 并发"锁"的本质(一步步实现锁)
Java Synchronized实现互斥之应用与源码初探
Java 对象头分析与使用(Synchronized相关)
Java Synchronized 偏向锁/轻量级锁/重量级锁的演变过程
Java Synchronized 重量级锁原理深入剖析上(互斥篇)
Java Synchronized 重量级锁原理深入剖析下(同步篇)
Java并发之 AQS 深入解析(上)
Java并发之 AQS 深入解析(下)
Java Thread.sleep/Thread.join/Thread.yield/Object.wait/Condition.await 详解
Java 并发之 ReentrantLock 深入分析(与Synchronized区别)
Java 并发之 ReentrantReadWriteLock 深入分析
Java Semaphore/CountDownLatch/CyclicBarrier 深入解析(原理篇)
Java Semaphore/CountDownLatch/CyclicBarrier 深入解析(应用篇)
最详细的图文解析Java各种锁(终极篇)
线程中断系列文章:
Java “优雅”地中断线程(实践篇)
Java “优雅”地中断线程(原理篇)
在Android开发中,不可避免的会用到线程来执行耗时任务,那如果我们想在中途停止/中断任务的执行,该怎么办呢?先来看看一个简单的线程。
private Thread threadOne = new Thread(new Runnable() {
@Override
public void run() {
try {
while(true) {
Log.d(TAG, "thread isAlive:" + threadOne.isAlive());
Thread.sleep(1000);
}
} catch (Exception e) {
Log.d(TAG, e.getClass().toString());
Log.d(TAG, "thread isAlive in catch:" + threadOne.isAlive());
}
}
});
正常运行打印结果:
当使用interrupt()方法中断该线程时,打印如下:
可以看出,调用interrupt()后,会捕获名为“InterruptedException”的异常,但是接下来的发现线程还存活,这是怎么回事呢?既然线程能够被中断,那么是否提供查询中断状态的方法呢?通过查看api我们发现,thread.isInterrupted()可以查看线程的中断状态,因此我们再加一个打印:
Log.d(TAG, e.getClass().toString());
Log.d(TAG, "thread isInterrupted::" + threadOne.isInterrupted());
Log.d(TAG, "thread isAlive in catch:" + threadOne.isAlive());
然而中断状态位依然是“未被中断”。这与我们想象的不太一样,因此回想一下是哪个方法抛出了异常,发现是sleep方法。
// BEGIN Android-changed: Implement sleep() methods using a shared native implementation.
public static void sleep(long millis) throws InterruptedException {
sleep(millis, 0);
}
我们先把sleep()方法注释掉,再运行
private Thread threadOne = new Thread(new Runnable() {
@Override
public void run() {
try {
while (!threadOne.isInterrupted()) {
Log.d(TAG, "thread isAlive:" + threadOne.isAlive());
}
Log.d(TAG, "break while thread isAlive:" + threadOne.isAlive());
Log.d(TAG, "break while thread isInterrupted::" + threadOne.isInterrupted());
} catch (Exception e) {
Log.d(TAG, e.getClass().toString());
Log.d(TAG, "thread isInterrupted::" + threadOne.isInterrupted());
Log.d(TAG, "thread isAlive in catch:" + threadOne.isAlive());
}
}
});
这次的结果比较符合我们的“直观想象”, 线程还是存活,但中断状态位标记位为true。
从上面两个两个例子可知:
1.中断正在阻塞(sleep)的线程,会抛出InterruptedException异常,中断标记位为false。
2.中断未被阻塞的线程,中断标记位会被置为true。
那么针对正在阻塞的线程,我们只需要捕获到InterruptedException异常就退出线程执行,对于未被阻塞的线程,判断中断标记是否为true,若是则退出线程执行。当然我们线程里如果调用了其它方法,不确定其它方法阻塞与否,因此可以将这两种判断结合起来,如下:
private Thread threadOne = new Thread(new Runnable() {
@Override
public void run() {
try {
while (!threadOne.isInterrupted()) {
doSomething();
}
} catch (Exception e) {
if (e instanceof InterruptedException) {
isInterrupted = true;
}
}
}
});
也许你会疑惑说你上面的线程都是在while里跑,如果线程只走一次,怎么中断呢?只能尽可能在每个关键之处停止其执行。
private Thread threadOne = new Thread(new Runnable() {
@Override
public void run() {
try {
if (!threadOne.isInterrupted())
doSomething1();
else
return;
if (!threadOne.isInterrupted())
doSomething2();
else
return;
if (!threadOne.isInterrupted())
doSomething3();
else
return;
} catch (Exception e) {
}
}
});
除了sleep方法之外,还有其它方法会抛出异常不?实际上,在Thread源码里对此都有解释,我们来看看源码怎么说的。
/**
* Interrupts this thread.
*
* 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.
*
*
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}.
*
*
If this thread is blocked in an I/O operation upon an {@link
* java.nio.channels.InterruptibleChannel InterruptibleChannel}
* 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}.
*
*
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.
*
*
If none of the previous conditions hold then this thread's interrupt
* status will be set.
总结来说:
- 在线程里使用sleep、wait、join等方法,当线程被中断时,中断状态位会被重置为false,并且抛出InterruptedException异常(这也是为什么我们第一个例子里thread.isInterrupted()为false的原因)
- 在线程里使用nio InterruptibleChannel接口时,当线程被中断时,中断状态位会被重置为true,并且抛出ClosedByInterruptException异常
- 在线程里使用nio Selector时,当线程被中断时,中断状态位会被重置为true
- 如不属于上述条件,则中断状态位会被重置为true(对应我们上面说的没有阻塞的情况)
thread.isInterrupted() 和 Thread.interrupted()区别
thread.isInterrupted()是对象方法,表示thread的中断状态。Thread.interrupted()是静态方法,表示当前线程的中断状态,举个例子:
Log.d(TAG, " curThread is:" + Thread.currentThread().getName());
Log.d(TAG, " Thread.currentThread().isInterrupted() before :" + Thread.currentThread().isInterrupted());
Log.d(TAG, " Thread.interrupted() before :" + Thread.interrupted());
Log.d(TAG, " threadOne.isInterrupted() before :" + threadOne.isInterrupted());
Thread.currentThread().interrupt();
Log.d(TAG, " Thread.currentThread().isInterrupted() after:" + Thread.currentThread().isInterrupted());
Log.d(TAG, " Thread.interrupted() after :" + Thread.interrupted());
Log.d(TAG, " Thread.currentThread().isInterrupted() after2:" + Thread.currentThread().isInterrupted());
Log.d(TAG, " threadOne.isInterrupted() after :" + threadOne.isInterrupted());
从上面可以看出来,Thread.interrupted()调用后会重置中断状态为false,而thread.isInterrupted()却不会。
总结
1、线程正在执行sleep、join、wait等方法,此时线程处在WAITING/TIMED_WAITING状态,当执行thread.interrupt(),那么会抛出InterruptedException异常,线程中断标记位为false,线程停止运行;
2、线程处在RUNNABLE状态,当执行thread.interrupt(),不会抛出异常,线程中断标记位为true,线程未停止运行;
3、如果线程处在BLOCKED(Synchronized争抢锁)状态,当执行thread.interrupt(),不会抛出异常,线程中断标记位为true,线程未停止运行(这点也说明了Synchronized不可打断)
更多关于线程状态的问题请移步:Java 线程状态