线程的定义给我们提供了并发执行多个任务的方式,大多数情况下我们会让每个任务都自行执行结束,这样能保证事务的一致性,但是有时我们希望在任务执行中取消任务,使线程停止。在java中要让线程安全、快速、可靠地停下来并不是一件容易的事,java也没有提供任何可靠的方法终止线程的执行。

线程调度策略中有抢占式和协作式两个概念,与之类似的是中断机制也有协作式和抢占式。

历史上Java曾经使用stop()方法终止线程的运行,他们属于抢占式中断。但它引来了很多问题,早已被JDK弃用。调用stop()方法则意味着

①将释放该线程所持的所有锁,而且锁的释放不可控。

②即刻将抛出ThreadDeath异常,不管程序运行到哪里,但它不总是有效,如果存在被终止线程的锁竞争;

第一点将导致数据一致性问题,这个很好理解,一般数据加锁就是为了保护数据的一致性,而线程停止伴随所持锁的释放,很可能导致被保护的数据呈现不一致性,最终导致程序运算出现错误。第二点比较模糊,它要说明的问题就是可能存在某种情况stop()方法不能及时终止线程,甚至可能终止不了线程。看如下代码会发生什么情况,看起来线程mt因为执行了stop()方法将停止,按理来说就算execut方法是一个死循环,只要执行了stop()方法线程将结束,无限循环也将结束。其实不会,因为我们在execute方法使用了synchronized修饰,同步方法表示在执行execute时将对mt对象进行加锁,另外,Thread的stop()方法也是同步的,于是在调用mt线程的stop()方法前必须获取mt对象锁,但mt对象锁被execute方法占用,且不释放,于是stop()方法永远获取不了mt对象锁,最后得到一个结论,使用stop()方法停止线程不可靠,它未必总能有效终止线程。

public class ThreadStop {
public static voidmain(String[] args) {
           Thread mt= new MyThread();
           mt.start();
           try {
                    Thread.currentThread().sleep(100);
           } catch(InterruptedException e) {
                    e.printStackTrace();
           }
           mt.stop();
}

static classMyThread extends Thread {
           publicvoid run() {
                    execute();
           }
           privatesynchronized void execute() {
                    while(true) {
                    }
           }
}
}

经历了很长时间的发展,Java最终选择用一种协作式的中断机制实现中断。协作式中断的原理很简单,其核心是先对中断标识进行标记,某线程设置某线程的中断标识位,被标记了中断位的线程在适当的时间节点会抛出异常,捕获异常后做相应的处理。实现协作中断有三个要点需要考虑:①是在Java层面实现轮询中断标识还是在JVM中实现;②轮询的颗粒度的控制,一般颗粒度要尽量小周期尽量短以保证响应的及时性;③轮询的时间节点的选择,其实就是在哪些方法里面轮询,例如JVM将Thread类的wait()、sleep()、join()等方法都实现中断标识的轮询操作。

中断标识放在哪里?中断是针对线程实例而言,从Java层面上看,标识变量放到线程中肯定再合适不过了,但由于由JVM维护,所以中断标识具体由本地方法维护。在Java层面仅仅留下几个API用于操作中断标识,如下,

public class Thread{
    public voidinterrupt() {……}
    public BooleanisInterrupted() {……}
    public static Booleaninterrupted() {……}
}

上面三个方法依次用于设置线程为中断状态、判断线程状态是否中断、清除当前线程中断状态并返回它之前的值。通过interrupt()方法设置中断标识,假如在非阻塞线程则仅仅只是改变了中断状态,线程将继续往下运行,但假如在可取消阻塞线程中,如正在执行sleep()、wait()、join()等方法的线程则会因为被设置了中断状态而抛出InterruptedException异常,程序对此异常捕获处理。

上面提到的三个要点,第一是轮询在哪个层面实现,这个没有特别的要求,在实际中只要不出现逻辑问题,在Java层面或JVM层面实现都是可以的,例如常用的线程睡眠、等待等操作是通过JVM实现,而java并发框架工具里面的中断则放到Java实现,不管在哪个层面上去实现,在轮询过程中都一定要能保证不会产生阻塞。第二是要保证轮询的颗粒度尽可能的小周期尽可能短,这关系到中断响应的速度。第三点是关于轮询的时间节点的选取。

针对三要点来看看java并发框架中是如何支持中断的,主要在等待获取锁的过程中提供中断操作,下面是伪代码。只需增加加红加粗部分逻辑即可实现中断支持,在循环体中每次循环都对当前线程中断标识位进行判断,一旦检查到线程被标记为中断则抛出InterruptedException异常,高层代码对此异常捕获处理即完成中断处理。总结起来就是java并发工具获取锁的中断机制是在Java层面实现的,轮询时间节点选择在不断做尝试获取锁操作过程中,每个循环的颗粒度比较小,响应速度得以保证,且循环过程不存在阻塞风险,保证中断检测不会失效。

if(尝试获取锁失败) {
    创建node
    使用CAS方式把node插入到队列尾部
    while(true){
    if(尝试获取锁成功并且 node的前驱节点为头节点){
把当前节点设置为头节点
    跳出循环
}else{
    使用CAS方式修改node前驱节点的waitStatus标识为signal
    if(修改成功){
        挂起当前线程
        if(当前线程中断位标识为true)
           抛出InterruptedException异常
}
}
}

判断线程是否处于中断状态其实很简单,只需使用Thread.interrupted()操作,如果为true则说明线程处于中断位,并清除中断位。至此java并发工具实现了支持中断的获取锁操作。

本文从java发展过程分析了抢占式中断及协作式中断,由于抢占式存在一些缺陷现在已不推荐使用,而协作式中断作为推荐做法,尽管在响应时间较长,但其具有无可比拟的优势。协作式中断我们可以在JVM层面实现,同样也可以在Java层面实现,例如JDK并发工具的中断即是在Java层面实现,不过如果继续深究是因为Java留了几个API供我们操作线程的中断标识位,这才使Java层面实现中断操作得以实现。            对于java的协作式中断机制有人肯定有人批评,批评者说java没有抢占式中断机制,且协作式中断机制迫使开发者必须维护中断状态,迫使开发者必须处理InterruptedException。但肯定者则认为,虽然协作式中断机制推迟了中断请求的处理,但它为开发人员提供更灵活的中断处理策略,响应性可能不及抢占式,但程序健壮性更强。

喜欢研究java的同学可以交个朋友:

从JDK源码角度看java并发线程的中断的更多相关文章

  1. 从JDK源码角度看java并发的公平性

    JAVA为简化开发者开发提供了很多并发的工具,包括各种同步器,有了JDK我们只要学会简单使用类API即可.但这并不意味着不需要探索其具体的实现机制,本文从JDK源码角度简单讲讲并发时线程竞争的公平性. ...

  2. 从JDK源码角度看java并发的原子性如何保证

    JDK源码中,在研究AQS框架时,会发现很多地方都使用了CAS操作,在并发实现中CAS操作必须具备原子性,而且是硬件级别的原子性,java被隔离在硬件之上,明显力不从心,这时为了能直接操作操作系统层面 ...

  3. 从JDK源码角度看Short

    概况 Java的Short类主要的作用就是对基本类型short进行封装,提供了一些处理short类型的方法,比如short到String类型的转换方法或String类型到short类型的转换方法,当然 ...

  4. 从JDK源码角度看Byte

    Java的Byte类主要的作用就是对基本类型byte进行封装,提供了一些处理byte类型的方法,比如byte到String类型的转换方法或String类型到byte类型的转换方法,当然也包含与其他类型 ...

  5. 从JDK源码角度看Object

    Java的Object是所有其他类的父类,从继承的层次来看它就是最顶层根,所以它也是唯一一个没有父类的类.它包含了对象常用的一些方法,比如getClass.hashCode.equals.clone. ...

  6. 从JDK源码角度看Boolean

    Java的Boolean类主要作用就是对基本类型boolean进行封装,提供了一些处理boolean类型的方法,比如String类型和boolean类型的转换. 主要实现源码如下: public fi ...

  7. 从JDK源码角度看并发竞争的超时

    JDK中的并发框架提供的另外一个优秀机制是锁获取超时的支持,当大量线程对某一锁竞争时可能导致某些线程在很长一段时间都获取不了锁,在某些场景下可能希望如果线程在一段时间内不能成功获取锁就取消对该锁的等待 ...

  8. 从JDK源码角度看线程池原理

    "池"技术对我们来说是非常熟悉的一个概念,它的引入是为了在某些场景下提高系统某些关键节点性能,最典型的例子就是数据库连接池,JDBC是一种服务供应接口(SPI),具体的数据库连接实 ...

  9. 从JDK源码角度看并发锁的优化

    在CLH锁核心思想的影响下,JDK并发包以CLH锁作为基础而设计,其中主要是考虑到CLH锁更容易实现取消与超时功能.比起原来的CLH锁已经做了很大的改造,主要从两方面进行了改造:节点的结构与节点等待机 ...

随机推荐

  1. Python笔记十一(迭代器)

    这里我们要学会Iterable和Iterator. 一类是集合数据类型,如list.tuple.dict.set.str等: 一类是generator,包括生成器和带yield的generator f ...

  2. ACM Ignatius and the Princess II

    Problem Description Now our hero finds the door to the BEelzebub feng5166. He opens the door and fin ...

  3. ACM 最小公倍数

    给定两个正整数,计算这两个数的最小公倍数. Input 输入包含多组测试数据,每组只有一行,包括两个不大于1000的正整数.Output对于每个测试用例,给出这两个数的最小公倍数,每个实例输出一行.  ...

  4. iOS开发基础之设置状态栏和二维码的unspported type found 问题

    最近遇到设置状态栏和二维码AVCaptureMetadataOutput setMetadataObjectTypes: unspported type found的错误问题,所以我在这里进行总结一下 ...

  5. 初识在Spring Boot中使用JPA

    前面关于Spring Boot的文章已经介绍了很多了,但是一直都没有涉及到数据库的操作问题,数据库操作当然也是我们在开发中无法回避的问题,那么今天我们就来看看Spring Boot给我们提供了哪些疯狂 ...

  6. Android线性布局

    线性布局 LinearLayout 是一个视图组,用于使所有子视图在单个方向(垂直或水平)保持对齐. 您可以使用 android:orientation 属性指定布局方向. LinearLayout ...

  7. 计算机网络中的TCP/IP模型

    Transmission Control Protocol/Internet Protocol的简写,中译名为传输控制协议/因特网互联协议,又名网络通讯协议,是Internet最基本的协议.Inter ...

  8. [端口扫描]S扫描器跨网段扫描

    最近看了下端口扫描,用了几款扫描器,nmap啊,x-sacn等.之前很少关注安全方面的东西,所以也比较菜. 其中有一款叫做 "S扫描器"的,扫描速度非常快,可以大网段的扫描,几十万 ...

  9. HDFS:NameNode、DataNode、SecondaryNameNode

    可以一句话描述 HDFS:把客户端的大文件存放在很多节点的数据块中. HDFS设计原则: 1,文件以块(block)方式存储: 2,通过副本机制提高可靠度和读取吞吐量: 3,每个区块至少分到三台Dat ...

  10. Java基本语法-----java注释

    1注释的作用 通过注释提高程序的可读性,是java程序的条理更加清晰,易于区分代码行与注释行.另外通常在程序开头加入作者,时间,版本,要实现的功能等内容注释,方便后来的维护以及程序员的交流. 2注释的 ...