想必大家都知道 interrupt()函数,从这个单词上【interrupt 英[ˌɪntəˈrʌpt],美[ˌɪntəˈrʌpt],desc:打断; 打扰; 插嘴; 打岔; 使暂停; 使中断; 阻断,遮挡】,看起来是中断的含义,那么它真的能让运行到一半的线程中止掉吗?
据上图根据官方解释,在调用 interrupt 函数以后,收到 InterruptedException 异常的前提是该线程“阻塞的调用 wait , sleep ,或 join 的方法”,那么假设某线程不在其列出的阻塞调用中,是否能使用此方法中止并抛出 InterruptedException 呢?实践出真知,直接操作一把,看能否中断便一目了然。
1 | /** |
根据伪代码执行并不能中止,也不会抛出 InterruptedException,如按官方文档所描述,增加调用阻塞方法即可得到 InterruptedException,【需要注意的是在阻塞的情况中断,不仅会得到中断异常,还会恢复中断标志位】但是子线程还是会继续输出“exit”,那么如何中断?
1 | import java.util.concurrent.TimeUnit; |
由此上代码,捕获了 InterruptedException,然后手动停止了当前线程,到此不难理解 interrupt 函数向某个线程发送中断请求,这个请求操作是将被请求线程的中断标志位设置为 true,重要的是它仅仅只是发送中断请求,至于这个线程能否被中断,这取决于被请求线程是否检查了中断标志位[可以使用 isInterrupted()]或者对中断异常做出对应的响应。
java 线程的状态
1 | // java.lang.Thread.State |
java 线程状态的流转过程
此上我们不难看出如果调用了阻塞函数,线程都会进入 WAITTING/TIMED_WAITTING 状态,也只有在这个状态中的线程,我们才可以进行响应中断,所以如果你看到这里,你可以将其 interrupt 函数理解为“唤醒一个轻量阻塞的线程”。
为什么处于 BLOCKED 状态的线程不能响应中断?大家可以去看看 Thread 提供的阻塞函数中,InterruptedException 作为一个检查异常而存在,也就是相当于我们在方法中 throw new InterruptedException();而 synchronized 作为一个关键字,是无法抛出检查异常的。而一个线程排查死锁问题,其不会一致处在 BLOCKED 状态中的,只有等 BLOCKED 恢复到运行状态,通过响应中断进行处理。
除了可以捕获 InterruptedException 进行响应中断,我们还可以根据提供的两个方法进行检查中断标志位,如下。
在大部分场景下,我们一旦检测到中断,会进行响应处理,也就是此次的中断我已经响应并处理了,如果不清除,那么假设线程还在执行,程序上不仅要处理循环检查等问题,还无法判断中断次数,清除本次中断标志,有助于感知下次中断。
这个问题取决于你的逻辑处理,甚至你可以输出摘要日志进行人工补偿,但其处理方式,笔者认为跟其他处理异常方式无差别,当你明确知道如何处理这个异常的时候就可以捕获,如果当前处理不了就抛出或者忽略。
中断其目的是为了优雅的停止某个线程,能让线程感知自身已经被列为中断线程,然后进行一些必要逻辑的处理。假设没有响应中断,直接调用 Thread.stop() 方法【已过时】,线程直接被杀掉,那么这个线程可能拥有的资源因为非正常的关闭导致一系列的问题发生。
使用自创建的线程跟使用线程池有啥区别?提到线程池是不是脑海中闪现了创建线程池的那几个核心参数、工作流程、线程池的复用、拒绝机制、缓冲机制等,这些理论知识点想必也牢记许久了。虽然线程池支持在虚拟机进程接受到退出命令后可以进行 shutdown。那么 shutdown 跟线程中断又有什么区别?在运行中的线程能否直接 kill 掉?我们能否监听关闭事件进行补偿?
回答这个问题之前需要先思考,中断会有什么影响?如果直接 kill 掉,那么这个线程使用的所有资源能被正常释放吗?我们虽然 interrupt()方法是中断线程的没错,但是它也仅仅是将线程的中断位设置为 true,它不会停止线程,而是需要用户自己去监视线程的状态为并做对应的处理。可能你会说 stop()方法,这个方法已经被废弃掉了,这种显示的停止的方法带来的问题会更多,你可以想象一下会有哪些影响?言归正传,如果线程没有对中断进行处理,仅仅是调用 interrupt()是不会停止的,如下。
1 | public static void main(String[] args) { |
前面说了这么多,现在看来,抛开直接 KILL 掉程序不说,首先我们要保证程序在正常的重启期间,任务是不能丢失的,你可能先想到是实现 Hook 方法,在程序关闭的时候触发收尾工作,来保证线程池的正常关闭。老师傅说可以交给 Spring 管理即可,思考了一下也是,我们只需要实现 destroy 方法,然后 shutdown 即可。既然想到了 Spring,那 Spring 的线程池是否处理了 shutdown 方法,那我们来探究一下,日常使用 Spring 线程池的时候最常见的一行配置你肯定见过,如下。
1 | <bean id="taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor"> |
1 | public class ThreadPoolTaskExecutor extends ExecutorConfigurationSupport implements AsyncListenableTaskExecutor, SchedulingTaskExecutor { |
InitializingBean 跟 DisposableBean 这不经常见么,是的没错,凡是继承 InitializingBean 跟 DisposableBean 的 Bean,会在 bean 的初始化跟销毁的时候调用对应的 afterPropertiesSet()跟 destory()方法。具体直接上一张图搞定。
初始化方法暂且抛开不说,因为我们这里探究的是销毁方法。直接上代码。
1 | public void destroy() { |
由此上我们可以看到,使用 Spring 线程池可以最大程度上解决任务丢失的问题,当然我们可以通过实现 DisposableBean 接口来自定义销毁的操作。那么还有更优雅的处理方式吗?
我相信很多童鞋都用过 Guava,Guava 中包含了许多并发类,同时也包含了几个方便的线程池相关的 ExecuorService 的实现,但需要注意的是这些实现类都无法通过直接创建或者子类化来创建实例,但是我们可以通过 MoreExecuors 来创建它们,MoreExecuors 中的方法如下
1 | <dependency> |
1 | //获得一个跟随JVM关闭而关闭的线程池 |
源码分析
1 | //关键源码 |
1 | final void addDelayedShutdownHook(final ExecutorService service, final long terminationTimeout, final TimeUnit timeUnit) { |
1 | //方法使用 |
源码分析
1 | //关键源码,此处调用的addDelayedShutdownHook跟上面的一样,只不过这里没有改变ThreadFactory,只设置了钩子线程 |
1 | //使用方法 |
源码分析
1 |
|
1 | //1 |
1 | //2 |
为什么这里要说电调,因为电调对于多旋翼来说肯定是响应越快越好,多轴要的就是快速响应!但快速响应等高性能就意味着价格高,所以我们在价格和性能之间往往是不好选择的(就是因为穷,哈哈哈哈),但 BLHELI 电调开源固件解决了这个问题。我们只需要花 40-50 块钱买一个好盈天行者 20A/40A 电调,然后往电调里刷 BLHELI 的电调固件,然后你就得到了一个响应速度可以和 200-300 块钱的高价电调相当的廉价电调!话说回来这 BLHELI 固件也不是非刷不可,不刷就是响应慢了点,照样能飞起来!好盈天行者 20A 和好盈天行者 40A,选哪个都行,看你资金情况和长远考虑,其实好盈天行者 20A 就目前来说就够用了! 电调原固件可以,但响应不快!刷了 BLHeli 固件之后响应速度大大提高,这会让你四轴飞行更加稳定!
GPS,其实应该是指 Global Navigation Satellite System 全球导航卫星系统,即 GNSS,主要是用来定位的系统,目前全球定位系统有下面几种:
tag:
缺失模块。
1、请确保node版本大于6.2
2、在博客根目录(注意不是yilia根目录)执行以下命令:
npm i hexo-generator-json-content --save
3、在根目录_config.yml里添加配置:
jsonContent: meta: false pages: false posts: title: true date: true path: true text: false raw: false content: false slug: false updated: false comments: false link: false permalink: false excerpt: false categories: false tags: true