线程中断探究:通过LockSupport方法引出的一系列思考
目录
前言
上一篇详细记录了学习AQS源码过程,其中提到LockSupport.park()挂起线程后,其它线程会唤醒unpark或中断interrupt方法来操作挂起的线程。此时就引出一些问题:
unpark唤醒与interrupt中断是否一样?
中断LockSupport挂起的线程会不会接收到异常?
中断到底是什么概念?
什么时候会抛出InterruptedExcetion?
一、线程中断概念
先看下中断是什么。大部分情况下,我们都会等待线程运行直到结束,或者让它们自行停止。然而,有时候我们希望提前结束线程。要使任务和线程能安全、快速、可靠地停止下来并不容易。Java并没有提供任何机制来安全的终止线程。但它提供了中断机制来终止线程。这是一种协作机制,能够使一个线程尝试去终止另一个线程的当前工作。一般情况下,处理中断时还是要结合业务场景自行决定如何处理。
二、几个中断方法探究
Thread类里涉及到中断的方法有3个:
interrupt():尝试中断线程(不一定立即线程运行,会设置线程的中断状态为true)
interrupted():判断目标线程的中断状态,清除当前线程的中断状态。也就是第一次调用该方法时为true,第二次调用该方法时会返回false
isInterrupted():判断目标线程的是否中断过,再次调用也不会改变
2.1 interrupt
public void interrupt() {
if (this != Thread.currentThread())// 根据javadoc描述,只有当前线程或者其它线程有权限操作当前线程时会被允许中断,否则抛出SecurityException
checkAccess();
synchronized (blockerLock) {//如果有可中断的I/O操作中阻塞线程的对象
Interruptible b = blocker;
if (b != null) {
interrupt0(); //只设置中断标识
b.interrupt(this);
return;
}
}
interrupt0();//只设置中断标识
}
根据源码逻辑,interrupt()只是设置线程的中断标识,具体实现可参考openjdk 8.41版本里hotspot的实现逻辑
//参考openjdk8.41版本下hotspot\src\share\vm\runtime\thread.cpp文件
void Thread::interrupt(Thread* thread) {
trace("interrupt", thread);
debug_only(check_for_dangling_thread_pointer(thread);)
os::interrupt(thread);//这个方法去执行中断逻辑
}
//参考openjdk8.41版本下的hotspot\src\os\linux\vm\os_linux.cpp文件
void os::interrupt(Thread* thread) {
assert(Thread::current() == thread || Threads_lock->owned_by_self(),
"possibility of dangling Thread pointer");
OSThread* osthread = thread->osthread();//获得本地线程
if (!osthread->interrupted()) {//判断本地线程对象是否为中断
osthread->set_interrupted(true);//设置中断状态为true
OrderAccess::fence();
ParkEvent * const slp = thread->_SleepEvent ;
if (slp != NULL) slp->unpark() ;
}
// For JSR166. Unpark even if interrupt status already was set
if (thread->is_Java_thread())
((JavaThread*)thread)->parker()->unpark();
ParkEvent * ev = thread->_ParkEvent ;
if (ev != NULL) ev->unpark() ;
}
2.2 interrupted及isInterrupted
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
public boolean isInterrupted() {
return isInterrupted(false);
}
private native boolean isInterrupted(boolean ClearInterrupted);
根据上面的java源码逻辑,interruted及isInterrupted()都调了isInterrupted(clearInterrupted)的native方法。从这个方法在openjdk中的hotspot虚拟机实现,能清楚的了解到如果不清除状态会直接返回中断标识;对于清除状态为true的会设置中断标识为false,即清除了之前的true状态。
//参考openjdk8.41版本下的hotspot\src\share\vm\runtime\thread.cpp文件
bool Thread::is_interrupted(Thread* thread, bool clear_interrupted) {
trace("is_interrupted", thread);
debug_only(check_for_dangling_thread_pointer(thread);)
// 注意:如果clear_interrupted==false,这只是获取并返回字段osthread()->interrupted()的值
return os::is_interrupted(thread, clear_interrupted);//这个方法是真正逻辑
}
//参考openjdk8.41版本下的hotspot\src\os\linux\vm\os_linux.cpp文件
bool os::is_interrupted(Thread* thread, bool clear_interrupted) {
assert(Thread::current() == thread || Threads_lock->owned_by_self(),
"possibility of dangling Thread pointer");
OSThread* osthread = thread->osthread();
bool interrupted = osthread->interrupted();//判断本地线程是否中断
if (interrupted && clear_interrupted) {//线程中断且需要清理标记为true时
osthread->set_interrupted(false);//设置中断状态为false,相当于清理了中断状态
}
return interrupted;
}
三、中断响应
本章不探讨不可中断的阻塞方法对中断的响应,只探讨可中断的阻塞方法对中断的响应,例如Thread.sleep,Thread.wait等和LockSupport阻塞机制对中断的响应,以下为示例:
3.1 可中断的阻塞方法对中断的响应
以Thread.sleep()方法被中断为例,代码示例如下:t1线程先执行,打印一句话后即睡眠5秒;在t1线程1秒后由t2线程中断t1线程。由打印结果看出,被t2中断的t1线程抛出InterruptedException异常。此处我没有处中断异常。
public class InterruptTest2
{
public void run1(){//测试阻塞线程中断响应
System.out.println("t1线程开始了");
try {
Thread.sleep(5000);
System.out.println("t1线程睡眠被打断了,这里还会处理吗?");//不会打印此句,后续操作取消
} catch (InterruptedException e) {
System.out.println("t1线程睡眠被打断了,中断状态为:"+Thread.interrupted());
Thread.currentThread().interrupt();//没有处理阻塞,只是恢复中断状态了。由调用线程来处理吧
}
System.out.println("t1线程中断状态是否恢复"+Thread.interrupted());
}
public static void main(String[] args) {
InterruptTest2 v = new InterruptTest2();
Thread t1 = new Thread(v::run1,"t1");
Thread t2 = new Thread(() ->{
t1.interrupt();
},"t2");
t1.start();
try {
TimeUnit.SECONDS.sleep(1L);
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
}
}
/**打印结果:
t1线程开始了
t1线程睡眠被打断了,中断状态为:false
t1线程中断状态是否恢复true
*/
当调用可中断的阻塞函数时,有两种实用策略可用处理InterruptedException:1、传递异常。如将InterruptedException添加到throws子句中。2、恢复中断状态。一般的写法类似我上面的处理逻辑,保存中断状态,取消抛出异常的后续代码逻辑。对于一些不支持取消但仍然可以调用可中断阻塞方法的操作,它们必须在循环中调用这些方法,并在发现中断后重新尝试。如我的上篇文章详解AQS时acquireQueued()的处理,这种处理建议调用的方法在阻塞或进行重要工作前先检查中断状态。
3.2LockSupport方法对中断的响应
以LockSupport.park()方法被中断为例,代码示例如下:t1线程先执行,打印一句话后开始挂起t1;在t1线程1秒后由t2线程中断t1线程。由打印结果看出,被t2中断的t1线程没有抛出InterruptedException异常。但是从if(Thread.interrupted())这个判断中发现确实是中断了挂起的t1线程,而且被中断的线程没有任何取消操作,继续执行后续代码,只是中断标识为true而已。示例中如果t2线程改用LockSupport.unpark()唤醒线程是不会改变中断状态的。
public class InterruptTest1
{
public static void main(String[] args) {
InterruptTest1 v = new InterruptTest1();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("这是t1线程开始了");
LockSupport.park(this);
if(Thread.interrupted()){
System.out.println("t1线程被阻塞过,中断状态:"+Thread.interrupted());
Thread.currentThread().interrupt();
System.out.println("t1线程自我中断,中断状态:"+Thread.interrupted());
}else{
System.out.println("t1线程没有阻塞");
}
}
},"t1");
Thread t2 = new Thread(() ->{
// LockSupport.unpark(t1);
t1.interrupt();
},"t2");
t1.start();
try {
TimeUnit.SECONDS.sleep(1L);
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
}
}
总结
回到本文的前言,再思考提出的问题,结合上面的探究,可总结以下几点:
- 线程中断并不是立即终止线程,java提供一种中断机制,中断机制是一种协作机制。
- 线程中断会设置中断状态为true,具体处理逻辑由被中断线程自己决定。
- 线程中断主要处理block状态。在阻塞库中有些方法可通过抛出InterruptedException来响应中断请求。对于LockSupport阻塞机制来说,会响应中断请求,但不会抛出InterruptedException。
- 对于可中断的阻塞函数,如sleep、wait等,在处理InterruptedException的两处实用策略:1、传递异常,2、恢复中断状态
线程的中断及处理是门大学问,本文只是探究了中断的机制。真正处理中断时还是要结合业务场景自行决定如何处理。说的规范点,任何任务代码不应该对其执行所在的线程的中断策略做出假设,线程应该只能由其所有者中断,所有者可以将线程的中断策略信息封装到某个合适的取消机制中。
参考:《JAVA并发编辑实战》
jdk1.8.0_201源码及javadoc
本文地址:https://blog.csdn.net/changlina_1989/article/details/110233153
下一篇: 天梯废物我是