线程状态转换
概述
在之前的文章中我们分析了线程的由来及并发线程带来的问题,不过并没有针对线程的状态进行过多的分析,而只是针对进程的状态做了简要的介绍。 本节就来介绍一下线程的状态及之间的状态转换。线程的状态相较于进程要稍微复杂一点,我们在描述进程状态的时候从是否持有cpu的角度来划分的, 线程的话,除了cpu之外还要考虑的就是锁,也就是说影响线程状态的两个因素是:cpu和锁
状态转换
还是用一张图来说明线程的状态转换,如下:
java中线程的状态类的定义如下:
1 | public enum State { |
结合上面的图和状态的定义我们从cpu和锁的角度来总结一下:
线程刚被创建出来的时候是处于new状态,在线程调用了start方法之后,就进入了runnable(就绪)的状态,这里就绪的意思是 完事具备只差cpu(当然如果有锁的话肯定也是要获取锁了),在线程获取到了cpu之后,就处于运行的状态了(此处不知道为什么没有running状态),由于线程的层面上 并不会像进程那样可以通过时钟中断来切换,因此需要有一种方式来让线程自己决定什么时候让出cpu,这个时候就用到了yield,不过yield 只会让出cpu,并不会让出锁(如果有的话), 上面在调用了start方法之后,在线程还没有处于runnable状态的时候,如果线程进入了一个被synchronized或者锁修饰的方法块的话,如果之前有线程 正在当前这个代码块运行的话,那么当前这个线程也就没有办法获取到锁了,这个时候线程其实是处于block状态的,类比于进程,线程的block状态是除了cpu之外 还在等待其他的资源,从这一点来看两者是一样的。处于runnable状态的线程,在没有锁的情况下,不能使用wait的方法,可以使用join、sleep方法, 在有锁的情况下可以使用的方法:sleep、join、wait、park,至于不能使用wait方法的原因,是因为wait方法并不是thread提供的方法,而是Object方法提供的 该方法要求当前线程要持有锁,因此只有处于运行状态的并且持有锁的线程才可以使用这个方法。对于持有锁的线程,我们看一下调用了这几个相关的方法后分别会发生什么。 sleep、join会释放cpu,但是并不会释放锁,wait、park则会释放锁也会释放cpu。
线程的中断
说明:线程的中断是一个不大不小的话题,后面如果有必要的话单独整理一篇,现在就放在这里了。
首先,线程的不应该由其他的线程中断或停止,试想一下如果一个获取了锁的线程还没有来得及释放锁突然被终止了,这会导致后面的线程都没有办法再次获取 这个锁了,因此Thread的stop、suspend等方法都被废弃了,取而代之的是interrupt方法,不过这里的interrupt并不是中断或者停止线程,而是设置一个中断 标记位,让被中断的线程在适当的时候自行决定如何处理。
我们可以将interrupt想像成一个邮箱,在调用当前线程的interrupt的时候有点像是给当前的线程发送了一封信件,告诉当前线程,你运行的时间太久了,要终止了! 如果当前这个线程在waiting、timed_waiting状态(也就是并非运行的状态),那么当前这个线程会立即退出当前的状态,并抛出一个interruptException, 这种情况下线程需要知道如何处理这种抛出的异常,或者抛出该异常让调用者来进行处理。 如果当前这个线程处于运行的状态,那么,仅仅是当前线程的中断标志位会变为true,线程的正常运行并不会受到影响。那么问题来了,这个interrupt应该如何使用呢?
- 方式1:interrupt中断的是线程的业务逻辑,需要线程周期性的检查自己的中断状态,线程需要对interruptException进行处理或者抛出给调用者进行处理
- 方式2:结合volatile来处理,这个时候通过周期性的检查该变量来判断是否需要终止,在线程退出之前可以选择将中断标记位清除(清除标记位可以理解为将信箱清空,方便接受后面再次发过来的信件)
具体实例如下:
1 | package com.sync; |
最终的执行结果如下
1 | thread is running in block1 |
个人觉得还是用volatile控制的力度会更好一点,不过二者都可以
总结
上面针对线程的状态做了简要的分析以便后续查看,后面会根据实际的情况持续更新
参考文章: https://blog.csdn.net/pange1991/article/details/53860651 https://www.cnblogs.com/aspirant/p/8876670.html