Java并发编程原理与实战四:线程如何中断
如果你使用过杀毒软件,可能会发现全盘杀毒太耗时间了,这时你如果点击取消杀毒按钮,那么此时你正在中断一个运行的线程。
java为我们提供了一种调用interrupt()方法来请求终止线程的方法,下面我们就一起来学习一下线程的中断。
每一个线程都有一个boolean类型标志,用来表明当前线程是否请求中断,当一个线程调用interrupt() 方法时,线程的中断标志将被设置为true。
我们可以通过调用Thread.currentThread().isInterrupted()或者Thread.interrupted()来检测线程的中断标志是否被置位。这两个方法的区别是
Thread.currentThread().isInterrupted()是线程对象的方法,调用它后不清除线程中断标志位;而Thread.interrupted()是一个静态方法,调用它会清除
线程中断标志位。
Thread.currentThread().isInterrupted(): 对象方法 不清除中断标志位
Thread.interrupted(): 静态方法 清除中断标志位(设置为false)
所以说调用线程的interrupt() 方法不会中断一个正在运行的线程,这个机制只是设置了一个线程中断标志位,如果在程序中你不检测线程中断标志位,那么即使
设置了中断标志位为true,线程也一样照常运行。
一般来说中断线程分为三种情况:
(一) :中断非阻塞线程
(二):中断阻塞线程
(三):不可中断线程
(一) :中断非阻塞线程
中断非阻塞线程通常有两种方式:
(1)采用线程共享变量
这种方式比较简单可行,需要注意的一点是共享变量必须设置为volatile,这样才能保证修改后其他线程立即可见。
public class InterruptThreadTest extends Thread{
// 设置线程共享变量
volatile boolean isStop = false;
public void run() {
while(!isStop) {
long beginTime = System.currentTimeMillis();
System.out.println(Thread.currentThread().getName() + "is running");
// 当前线程每隔一秒钟检测一次线程共享变量是否得到通知
while (System.currentTimeMillis() - beginTime < 1000) {}
}
if (isStop) {
System.out.println(Thread.currentThread().getName() + "is interrupted");
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
InterruptThreadTest itt = new InterruptThreadTest();
itt.start();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 线程共享变量设置为true
itt.isStop = true;
}
}
(2) 采用中断机制
代码如下:
public class InterruptThreadTest2 extends Thread{
public void run() {
// 这里调用的是非清除中断标志位的isInterrupted方法
while(!Thread.currentThread().isInterrupted()) {
long beginTime = System.currentTimeMillis();
System.out.println(Thread.currentThread().getName() + "is running");
// 当前线程每隔一秒钟检测线程中断标志位是否被置位
while (System.currentTimeMillis() - beginTime < 1000) {}
}
if (Thread.currentThread().isInterrupted()) {
System.out.println(Thread.currentThread().getName() + "is interrupted");
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
InterruptThreadTest2 itt = new InterruptThreadTest2();
itt.start();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 设置线程的中断标志位
itt.interrupt();
}
}
(二):中断阻塞线程
当线程调用Thread.sleep()、Thread.join()、object.wait()再或者调用阻塞的i/o操作方法时,都会使得当前线程进入阻塞状态。那么此时如果在线程处于阻塞状态是调用
interrupt() 方法设置线程中断标志位时会出现什么情况呢! 此时处于阻塞状态的线程会抛出一个异常,并且会清除线程中断标志位(设置为false)。这样一来线程就能退出
阻塞状态。当然抛出异常的方法就是造成线程处于阻塞状态的Thread.sleep()、Thread.join()、object.wait()这些方法。
代码实例如下:
public class InterruptThreadTest3 extends Thread{
public void run() {
// 这里调用的是非清除中断标志位的isInterrupted方法
while(!Thread.currentThread().isInterrupted()) {
System.out.println(Thread.currentThread().getName() + " is running");
try {
System.out.println(Thread.currentThread().getName() + " Thread.sleep begin");
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + " Thread.sleep end");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
//由于调用sleep()方法清除状态标志位 所以这里需要再次重置中断标志位 否则线程会继续运行下去
Thread.currentThread().interrupt();
e.printStackTrace();
}
}
if (Thread.currentThread().isInterrupted()) {
System.out.println(Thread.currentThread().getName() + "is interrupted");
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
InterruptThreadTest3 itt = new InterruptThreadTest3();
itt.start();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 设置线程的中断标志位
itt.interrupt();
}
}
需要注意的地方就是 Thread.sleep()、Thread.join()、object.wait()这些方法,会检测线程中断标志位,如果发现中断标志位为true则抛出异常并且将中断标志位设置为false。
所以while循环之后每次调用阻塞方法后 都要在捕获异常之后,调用Thread.currentThread().interrupt()重置状态标志位。
(三):不可中断线程
有一种情况是线程不能被中断的,就是调用synchronized关键字和reentrantLock.lock()获取锁的过程。
但是如果调用带超时的tryLock方法reentrantLock.tryLock(longtimeout, TimeUnit unit),那么如果线程在等待时被中断,将抛出一个InterruptedException异常,这是一个非常
有用的特性,因为它允许程序打破死锁。你也可以调用reentrantLock.lockInterruptibly()方法,它就相当于一个超时设为无限的tryLock方法。
public class InterruptThreadTest5 {
public void deathLock(Object lock1, Object lock2) {
try {
synchronized (lock1) {
System.out.println(Thread.currentThread().getName()+ " is running");
// 让另外一个线程获得另一个锁
Thread.sleep(10);
// 造成死锁
synchronized (lock2) {
System.out.println(Thread.currentThread().getName());
}
}
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName()+ " is interrupted");
e.printStackTrace();
}
}
public static void main(String [] args) {
final InterruptThreadTest5 itt = new InterruptThreadTest5();
final Object lock1 = new Object();
final Object lock2 = new Object();
Thread t1 = new Thread(new Runnable(){
public void run() {
itt.deathLock(lock1, lock2);
}
},"A");
Thread t2 = new Thread(new Runnable(){
public void run() {
itt.deathLock(lock2, lock1);
}
},"B");
t1.start();
t2.start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 中断线程t1、t2
t1.interrupt();
t2.interrupt();
}
}
其它学习文章参考:
Java并发编程原理与实战四:线程如何中断的更多相关文章
- Java并发编程原理与实战四十二:锁与volatile的内存语义
锁与volatile的内存语义 1.锁的内存语义 2.volatile内存语义 3.synchronized内存语义 4.Lock与synchronized的区别 5.ReentrantLock源码实 ...
- Java并发编程原理与实战四十三:CAS ---- ABA问题
CAS(Compare And Swap)导致的ABA问题 问题描述 多线程情况下,每个线程使用CAS操作欲将数据A修改成B,当然我们只希望只有一个线程能够正确的修改数据,并且只修改一次.当并发的时候 ...
- Java并发编程原理与实战四十一:重排序 和 happens-before
一.概念理解 首先我们先来了解一下什么是重排序:重排序是指编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段. 从Java源代码到最终实际执行的指令序列,会分别经历下面3种重排序,如下图 ...
- Java并发编程原理与实战四十:JDK8新增LongAdder详解
传统的原子锁AtomicLong/AtomicInt虽然也可以处理大量并发情况下的计数器,但是由于使用了自旋等待,当存在大量竞争时,会存在大量自旋等待,而导致CPU浪费,而有效计算很少,降低了计算效率 ...
- Java并发编程原理与实战四十五:问题定位总结
背景 “线下没问题的”. “代码不可能有问题 是系统原因”.“能在线上远程debug么” 线上问题不同于开发期间的bug,与运行时环境.压力.并发情况.具体的业务相关.对于线上的问题利用线上 ...
- Java并发编程原理与实战四十四:final域的内存语义
一.final域的重排序规则 对于final域,编译器和处理器要遵循两个重拍序规则: 1.在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序 ...
- Java并发编程原理与实战五:创建线程的多种方式
一.继承Thread类 public class Demo1 extends Thread { public Demo1(String name) { super(name); } @Override ...
- Java并发编程原理与实战二十五:ThreadLocal线程局部变量的使用和原理
1.什么是ThreadLocal ThreadLocal顾名思义是线程局部变量.这种变量和普通的变量不同,这种变量在每个线程中通过get和set方法访问, 每个线程有自己独立的变量副本.线程局部变量不 ...
- Java并发编程原理与实战十:单例问题与线程安全性深入解析
单例模式我想这个设计模式大家都很熟悉,如果不熟悉的可以看我写的设计模式系列然后再来看本文.单例模式通常可以分为:饿汉式和懒汉式,那么分别和线程安全是否有关呢? 一.饿汉式 先看代码: package ...
随机推荐
- Week-4-作业1
前言 经过了上周作业的学习拾遗,让我学到了很多东西,也能更好的阅读<构建之法>这本书,下面是我在阅读过第四章和第十七章之后想到的一些问题. 第四章 4.2.1 关于缩进,书中说用四个空格刚 ...
- React鼠标事件
说明:假设有一个用户名片,当鼠标滑到上面,显示用户详细信息,且用户详情卡片位置随鼠标位置改变而改变. UI框架:Material-ui 实现思路: 1.一个用户简介组件A(用于展示用户列表): 2.一 ...
- mysubmail 短信报警
https://www.mysubmail.com/chs/documents/developer/YPWD84 文本文档 官网:www.mysubmail.com 操作流程:快速接入短信 AP ...
- PAT 1042 字符统计
https://pintia.cn/problem-sets/994805260223102976/problems/994805280817135616 请编写程序,找出一段给定文字中出现最频繁的那 ...
- PHP 内置函数strlen 和mbstring扩展函数mb_strlen的区别
#EXAMPLE $str_uncode = "简体中文Chinese(Simplified)"; //统计字符串长度 echo strlen($str_uncode).'< ...
- java异常处理常见处理
反例之一:丢弃异常结论一:既然捕获了异常,就要对它进行适当的处理.不要捕获异常之后又把它丢弃,不予理睬. 反例之二:不指定具体的异常 结论二:在catch语句中尽可能指定具体的异常类型,必要时使用多个 ...
- 半夜思考, 为什么建议重写 equals() 方法时, 也要重写 hashCode() 方法
我说的半夜, 并不是真正的半夜, 指的是在我一个人的时候, 我会去思考一些奇怪的问题. 要理解 hashCode() 需要理解下面三个点: hash契约 哈希冲突 哈希可变 第一点: hash 契约指 ...
- Code Blocks中配置OpenGL
使用的文件:我的CSDN资源共享 将glut.h文件放到MinGw\include\GL目录下面 将glut32.dll文件放到C:\windows\system32目录下面(如果是64位操作系统的话 ...
- python自动化之图像
''' RGBA值:指定颜色中的红.绿.蓝和alpha(透明度)的值 RGBA 名称 (255,255,255,2 ...
- python自动化之正则
import re phoneNumRegex=re.compile(r'\d\d\d-\d\d\d-\d\d\d\d') mo=phoneNumRegex.search('My number is ...