线程中断探究:通过LockSupport方法引出的一系列思考

目录

前言

一、线程中断概念

二、几个中断方法探究

    2.1 interrupt

    2.2 interrupted及isInterrupted

三、中断响应

    3.1 可中断的阻塞方法对中断的响应

    3.2LockSupport方法对中断的响应

总结


前言

        上一篇详细记录了学习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

 

 

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值