并发编程专题-基础-7、Thread
1. 实现
2. 状态⭐️🔴
https://blog.51cto.com/u_15080031/4547025
2.1. 状态流转
结合线程状态解释一下执行过程。(状态装换参考自《深入理解 Java 虚拟机》)
- 新建(New),新建后尚未启动的线程
- 运行(Runnable),Runnable 包括了操作系统线程状态中的 Running 和 Ready
- 无限期等待(Waiting),不会被分配 CPU 执行时间,要等待被其他线程显式的唤醒。例如调用没有设置 Timeout 参数的 Object.wait() 方法
- 限期等待(Timed Waiting),不会被分配 CPU 执行时间,不过无需等待其他线程显示的唤醒,在一定时间之后会由系统自动唤醒。例如调用 Thread.sleep() 方法
- 阻塞(Blocked),线程被阻塞了,“阻塞状态”与“等待状态”的区别是:“阻塞状态”在等待获取着一个排他锁,这个事件将在另外一个线程放弃这个锁的时候发生,而“等待状态”则是在等待一段时间,或者唤醒动作的发生。在程序等待进入同步区域的时候,线程将进入这种状态
- 结束(Terminated):线程结束执行
2.2. 枚举
首先明确一点,当我们讨论一个线程的状态,指的是 Thread 类中 threadStatus 的值。
1 |
|
该值映射后对应的枚举为:
1 |
|
也就是说,线程的具体状态,看 threadStatus 就行了。
2.2.1. NEW
先要创建 Thread 类的对象,才能谈其状态。
1 |
|
这个时候,线程 t 就处于新建状态。但他还不是“线程”。
2.2.2. RUNNABLE
然后调用 start() 方法。
1 |
|
调用 start() 后,会执行一个 native 方法创建内核线程,以 linux 为例:
1 |
|
这时候才有一个真正的线程创建出来,并即刻开始运行。这个内核线程与线程 t 进行 1:1 的映射。这时候 t 具备运行能力,进入 RUNNABLE 状态。 RUNNABLE 可以细分为 READY 和 RUNNING,两者的区别只是是否等待到了资源并开始运行。
处于 RUNNABLE 且未运行的线程,会进入一个就绪队列中,等待操作系统的调度。处于就绪队列的线程都在等待资源,这个资源可以是 cpu 的时间片、也可以是系统的 IO。 JVM 并不关系 READY 和 RUNNING 这两种状态,毕竟上述的枚举类都不对 RUNNABLE 进行细分。
2.2.3. TERMINATED
当一个线程执行完毕(或者调用已经不建议的 stop 方法),线程的状态就变为 TERMINATED。进入 TERMINATED 后,线程的状态不可逆,无法再复活。
2.2.4. WAITING
等待中的线程状态,下面几个方法的调用会导致线程进入 WAITING 状态:
- Object.wait()
- Thread.join()
- LockSupport.park()
关于 BLOCKED、WAITING、TIMED_WAITING
BLOCKED、WAITING、TIMED_WAITING 都是带有同步语义的状态,我们先看一下 wait
和 notify
方法的底层实现。
2.2.4.1. wait 方法
1 |
|
总结:通过 object 获得内置锁 (objectMonitor),通过内置锁将 Thread 封装成 OjectWaiter 对象,然后 addWaiter 将它插入以 _waitSet 为首结点的等待线程链表中去,最后释放锁。
2.2.4.2. notify 方法
1 |
|
总结:通过 object 获得内置锁 (objectMonitor),调用内置锁的 notify 方法,通过 _waitset 结点移出等待链表中的首结点,将它置于 _EntrySet 中去,等待获取锁。注意:notifyAll 根据 policy 不同可能移入 _EntryList 或者 _cxq 队列中,此处不详谈。
通过 object 获得 objectMonitor,调用 objectMonitor 的 notify
方法。这个 notify 最后会走到 ObjectMonitor::DequeueWaiter
方法,获取 waitSet 列表中的第一个 ObjectWaiter 节点。并根据不同的策略,将取出来的 ObjectWaiter 节点,加入到 EntryList
或 cxq
中。 notifyAll
的实现类似于 notify
,主要差别在多了个 for 循环。
notify
和 notifyAll
并不会立即释放所占有的 ObjectMonitor 对象,其真正释放 ObjectMonitor 的时间点是在执行 monitorexit
指令。
一旦释放 ObjectMonitor
对象了,entryList
和 cxq
中的 ObjectWaiter 节点会依据 QMode
所配置的策略,通过 ExitEpilog 方法唤醒取出来的 ObjectWaiter 节点。被唤醒的线程,继续参与 monitor 的竞争。若竞争失败,重新进入 BLOCKED 状态,再回顾一下 monitor 的核心结构。
2.2.4.3. join 方法
1 |
|
join
的本质仍然是 wait()
方法。在使用 join
时,JVM 会帮我们隐式调用 notify
,因此我们不需要主动 notify 唤醒主线程。而 sleep()
方法最终是调用 SleepEvent
对象的 park 方法:
1 |
|
Thread.sleep
在 jvm 层面上是调用 thread 中 SleepEvent
对象的 park()
方法实现阻塞线程,在此过程中会通过判断时间戳来决定线程的睡眠时间是否达到了指定的毫秒。看到这里,对于 sleep
和 wait
的区别应该会有更深入的理解。
park
、unpark
方法也与同步语义无关。每个线程都与一个许可 (permit) 关联。unpark
函数为线程提供 permit,线程调用 park
函数则等待并消耗 permit。park 和 unpark 方法具体实现比较复杂,这里不展开。到此为止,我们可以整理出如下的线程状态转换图。
2.2.5. BLOCKED
等待 Monitor 锁的阻塞线程的线程状态,处于阻塞状态的线程正在等待 Monitor 锁进入 synchronized Block 或者 Method,或者在调用 Object.wait 后重新进入同步块/方法。简单的说,就是线程等待 synchronized 形式的锁时的状态
2.2.5.1. 与 Waiting 区别⭐️🔴
BLOCKED 和 WAITING 都是属于线程的阻塞等待状态。 BLOCKED 状态是指线程在等待监视器锁的时候的阻塞状态。 (如图)也就是在多个线程去竞争 Synchronized 同步锁的时候,没有竞争到锁资源的线程,会被阻塞等待,这个时候线程状态就是 BLOCKED。在线程的整个生命周期里面,只有 Synchronized 同步锁等待才会存在这个状态。
WAITING 状态,表示线程的等待状态,在这种状态下,线程需要等待某个线程的特定操作才会被唤醒。我们可以使用 Object.wait()、Object.join()、LockSupport.park() 这些方法使得线程进入到 WAITING 状态,在这个状态下,必须要等待特定的方法来唤醒,比如 Object.notify 方法可以唤醒 Object.wait()方法阻塞的线程 LockSupport.unpark()可以唤醒 LockSupport.park()方法阻塞的线程。
所以,在我看来,BLOCKED 和 WAITING 两个状态最大的区别有两个:
- BLOCKED 是锁竞争失败后被被动触发的状态,WAITING 是人为的主动触发的状态
- BLCKED 的唤醒时自动触发的,而 WAITING 状态是必须要通过特定的方法来主动唤醒以上就是我对这个问题的理解。
https://segmentfault.com/a/1190000039044989
https://blog.51cto.com/u_15080031/4547025
BLOCKED 状态可以视作是一种特殊的 WAITING,特指等待锁。
2.3. 查看状态
[[../../../../cubox/006-ChromeCapture/Java线程状态分析 Format’s Notes]]
2.4. 线程中断⭐️🔴
2.4.1. 什么是线程中断
而 Thread.interrupt 的作用其实也不是中断线程,而是「通知线程应该中断了」,具体到底中断还是继续运行,应该由被通知的线程自己处理。
具体来说,当对一个线程,调用 interrupt() 时,
① 如果线程处于被阻塞状态(例如处于 sleep, wait, join 等状态),那么线程将立即退出被阻塞状态,并抛出一个 InterruptedException 异常。仅此而已。
② 如果线程处于正常活动状态,那么会将该线程的中断标志设置为 true,仅此而已。被设置中断标志的线程将继续正常运行,不受影响。
interrupt() 并不能真正的中断线程,需要被调用的线程自己进行配合才行。
也就是说,一个线程如果有被中断的需求,那么就可以这样做
2.4.1.1. interrupt 与 interrupted 与 isInterrupted⭐️🔴
2.4.2. 如何中断线程
2.4.2.1. 线程休眠后如何唤醒线程 (唤醒线程的方法)
2.4.2.2. 中断补偿⭐️🔴
温故知新:并发基础-12、AQS
2.5. 优雅退出 (中断线程)⭐️🔴
2.5.1. volatile
2.5.2. atomicBoolean
2.5.3. interrupt+isInterrupted
2.5.3.1. 源码分析
3. start 线程开启 c 源码分析
4. 实战经验
https://juejin.cn/post/6857365822445191182
https://www.cnblogs.com/Chary/p/16522149.html
5. 参考与感谢
https://tech.youzan.com/javasuo-yu-xian-cheng-de-na-xie-shi/
https://fangjian0423.github.io/2016/06/04/java-thread-state/