欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-04-15 18:57:52
前言 上一篇详细记录了学习AQS源码过程,其中提到LockSupport.park()挂起线程后,其它线程会唤醒unpark或中断interrupt方法来操作挂起的线程。此时就引出一些问题:unpark唤醒与interrupt中断是否一样?中断LockSupport挂起的线程为什么没有接收到异常?中断到底是什么概念?什么时候会抛出InterruptedExcetion? 一、线程中断概念 先看下中断是什么。大部分情况下,我们都会等待线程运......

目录

前言

一、线程中断概念

二、几个中断方法探究

    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

 

 

本文地址:https://blog.csdn.net/changlina_1989/article/details/110233153