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

深入Java线程中断的本质与编程原则的概述

程序员文章站 2023-12-09 18:54:57
在历史上,java试图提供过抢占式限制中断,但问题多多,例如前文介绍的已被废弃的thread.stop、thread.suspend和 thread.resume等。另一方...
在历史上,java试图提供过抢占式限制中断,但问题多多,例如前文介绍的已被废弃的thread.stop、thread.suspend和 thread.resume等。另一方面,出于java应用代码的健壮性的考虑,降低了编程门槛,减少不清楚底层机制的程序员无意破坏系统的概率。

    如今,java的线程调度不提供抢占式中断,而采用协作式的中断。其实,协作式的中断,原理很简单,就是轮询某个表示中断的标记,我们在任何普通代码的中都可以实现。

例如下面的代码:
    volatile bool isinterrupted;
    //…
    while(!isinterrupted) {
        compute();
    }
    但是,上述的代码问题也很明显。当compute执行时间比较长时,中断无法及时被响应。另一方面,利用轮询检查标志变量的方式,想要中断wait和sleep等线程阻塞操作也束手无策。

    如果仍然利用上面的思路,要想让中断及时被响应,必须在虚拟机底层进行线程调度的对标记变量进行检查。是的,jvm中确实是这样做的。

下面摘自java.lang.thread的源代码:

        public static boolean interrupted() {
            return currentthread().isinterrupted(true);
        }
       //…
        private native boolean isinterrupted(boolean clearinterrupted);

可以发现,isinterrupted被声明为native方法,取决于jvm底层的实现。

    实际上,jvm内部确实为每个线程维护了一个中断标记。但应用程序不能直接访问这个中断变量,必须通过下面几个方法进行操作:

    public class thread {
        //设置中断标记
        public void interrupt() { ... } 
        //获取中断标记的值
        public boolean isinterrupted() { ... }
        //清除中断标记,并返回上一次中断标记的值
        public static boolean interrupted() { ... }  
    }

    通常情况下,调用线程的interrupt方法,并不能立即引发中断,只是设置了jvm内部的中断标记。因此,通过检查中断标记,应用程序可以做一些特殊操作,也可以完全忽略中断。

    你可能想,如果jvm只提供了这种简陋的中断机制,那和应用程序自己定义中断变量并轮询的方法相比,基本也没有什么优势。

    jvm内部中断变量的主要优势,就是对于某些情况,提供了模拟自动“中断陷入”的机制。

    在执行涉及线程调度的阻塞调用时(例如wait、sleep和join),如果发生中断,被阻塞线程会“尽可能快的”抛出interruptedexception。因此,我们就可以用下面的代码框架来处理线程阻塞中断:
    try {
        //wait、sleep或join
    }
    catch(interruptedexception e) {
        //某些中断处理工作
    }
    所谓“尽可能快”,我猜测jvm就是在线程调度调度的间隙检查中断变量,速度取决于jvm的实现和硬件的性能。   

    然而,对于某些线程阻塞操作,jvm并不会自动抛出interruptedexception异常。例如,某些i/o操作和内部锁操作。对于这类操作,可以用其他方式模拟中断:

    1)java.io中的异步socket i/o

    读写socket的时候,inputstream和outputstream的read和write方法会阻塞等待,但不会响应java中断。不过,调用socket的close方法后,被阻塞线程会抛出socketexception异常。

    2)利用selector实现的异步i/o

    如果线程被阻塞于selector.select(在java.nio.channels中),调用wakeup方法会引起closedselectorexception异常。

    3)锁获取

    如果线程在等待获取一个内部锁,我们将无法中断它。但是,利用lock类的lockinterruptibly方法,我们可以在等待锁的同时,提供中断能力。
    另外,在任务与线程分离的框架中,任务通常并不知道自身会被哪个线程调用,也就不知道调用线程处理中断的策略。所以,在任务设置了线程中断标记后,并不能确保任务会被取消。因此,有以下两条编程原则:
    1)除非你知道线程的中断策略,否则不应该中断它。

        这条原则告诉我们,不应该直接调用executer之类框架中线程的interrupt方法,应该利用诸如future.cancel的方法来取消任务。

    2)任务代码不该猜测中断对执行线程的含义。

        这条原则告诉我们,一般代码遇在到interruptedexception异常时,不应该将其捕获后“吞掉”,而应该继续向上层代码抛出。

    总之,java中的非抢占式中断机制,要求我们必须改变传统的抢占式中断思路,在理解其本质的基础上,采用相应的原则和模式来编程。