1. 实现

2. 状态⭐️🔴

image.png
image.png

image.png


https://blog.51cto.com/u_15080031/4547025

2.1. 状态流转

结合线程状态解释一下执行过程。(状态装换参考自《深入理解 Java 虚拟机》)

  1. 新建(New),新建后尚未启动的线程
  2. 运行(Runnable),Runnable 包括了操作系统线程状态中的 Running 和 Ready
  3. 无限期等待(Waiting),不会被分配 CPU 执行时间,要等待被其他线程显式的唤醒。例如调用没有设置 Timeout 参数的 Object.wait() 方法
  4. 限期等待(Timed Waiting),不会被分配 CPU 执行时间,不过无需等待其他线程显示的唤醒,在一定时间之后会由系统自动唤醒。例如调用 Thread.sleep() 方法
  5. 阻塞(Blocked),线程被阻塞了,“阻塞状态”与“等待状态”的区别是:“阻塞状态”在等待获取着一个排他锁,这个事件将在另外一个线程放弃这个锁的时候发生,而“等待状态”则是在等待一段时间,或者唤醒动作的发生。在程序等待进入同步区域的时候,线程将进入这种状态
  6. 结束(Terminated):线程结束执行

2.2. 枚举

首先明确一点,当我们讨论一个线程的状态,指的是 Thread 类中 threadStatus 的值。

1
private volatile int threadStatus = 0;  

该值映射后对应的枚举为:

1
2
3
4
5
6
7
8
public enum State {  
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}

也就是说,线程的具体状态,看 threadStatus 就行了。

2.2.1. NEW

先要创建 Thread 类的对象,才能谈其状态。

1
Thread t = new Thread();  

这个时候,线程 t 就处于新建状态。但他还不是“线程”。

2.2.2. RUNNABLE

然后调用 start() 方法。

1
t.start();  

调用 start() 后,会执行一个 native 方法创建内核线程,以 linux 为例:

1
2
3
4
5
private native void start0();

// 最后走到这
hotspot/src/os/linux/vm/os_linux.cpp
pthread_create(...);

这时候才有一个真正的线程创建出来,并即刻开始运行。这个内核线程与线程 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//1.调用ObjectSynchronizer::wait方法
void ObjectSynchronizer::wait(Handle obj, jlong millis, TRAPS) {
/*省略 */
//2.获得Object的monitor对象(即内置锁)
ObjectMonitor* monitor = ObjectSynchronizer::inflate(THREAD, obj());
DTRACE_MONITOR_WAIT_PROBE(monitor, obj(), THREAD, millis);
//3.调用monitor的wait方法
monitor->wait(millis, true, THREAD);
/*省略*/
}
//4.在wait方法中调用addWaiter方法
inline void ObjectMonitor::AddWaiter(ObjectWaiter* node) {
/*省略*/
if (_WaitSet == NULL) {
//_WaitSet为null,就初始化_waitSet
_WaitSet = node;
node->_prev = node;
node->_next = node;
} else {
//否则就尾插
ObjectWaiter* head = _WaitSet ;
ObjectWaiter* tail = head->_prev;
assert(tail->_next == head, "invariant check");
tail->_next = node;
head->_prev = node;
node->_next = head;
node->_prev = tail;
}
}
//5.然后在ObjectMonitor::exit释放锁,接着 thread_ParkEvent->park 也就是wait

总结:通过 object 获得内置锁 (objectMonitor),通过内置锁将 Thread 封装成 OjectWaiter 对象,然后 addWaiter 将它插入以 _waitSet 为首结点的等待线程链表中去,最后释放锁。

2.2.4.2. notify 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//1.调用ObjectSynchronizer::notify方法
void ObjectSynchronizer::notify(Handle obj, TRAPS) {
/*省略*/
//2.调用ObjectSynchronizer::inflate方法
ObjectSynchronizer::inflate(THREAD, obj())->notify(THREAD);
}
//3.通过inflate方法得到ObjectMonitor对象
ObjectMonitor * ATTR ObjectSynchronizer::inflate (Thread * Self, oop object) {
/*省略*/
if (mark->has_monitor()) {
ObjectMonitor * inf = mark->monitor() ;
assert (inf->header()->is_neutral(), "invariant");
assert (inf->object() == object, "invariant") ;
assert (ObjectSynchronizer::verify_objmon_isinpool(inf), "monitor is inva;lid");
return inf
}
/*省略*/
}
//4.调用ObjectMonitor的notify方法
void ObjectMonitor::notify(TRAPS) {
/*省略*/
//5.调用DequeueWaiter方法移出_waiterSet第一个结点
ObjectWaiter * iterator = DequeueWaiter() ;
//6.后面省略是将上面DequeueWaiter尾插入_EntrySet的操作
/**省略*/
}

总结:通过 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public final synchronized void join(long millis) throws InterruptedException {  
...
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}

join 的本质仍然是 wait() 方法。在使用 join 时,JVM 会帮我们隐式调用 notify,因此我们不需要主动 notify 唤醒主线程。而 sleep() 方法最终是调用 SleepEvent 对象的 park 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
int os::sleep(Thread* thread, jlong millis, bool interruptible) {  
//获取thread中的_SleepEvent对象
ParkEvent * const slp = thread->_SleepEvent ;
...
//如果是允许被打断
if (interruptible) {
//记录下当前时间戳,这是时间比较的基准
jlong prevtime = javaTimeNanos();
for (;;) {
//检查打断标记,如果打断标记为true,则直接返回
if (os::is_interrupted(thread, true)) {
return OS_INTRPT;
}
//线程被唤醒后的当前时间戳
jlong newtime = javaTimeNanos();
//睡眠毫秒数减去当前已经经过的毫秒数
millis -= (newtime - prevtime) / NANOSECS_PER_MILLISEC;
//如果小于0,那么说明已经睡眠了足够多的时间,直接返回
if (millis <= 0) {
return OS_OK;
}
//更新基准时间
prevtime = newtime;
//调用_SleepEvent对象的park方法,阻塞线程
slp->park(millis);
}
} else {
//如果不能打断,除了不再返回OS_INTRPT以外,逻辑是完全相同的
for (;;) {
...
slp->park(millis);
...
}
return OS_OK ;
}
}

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 同步锁等待才会存在这个状态。

image.png

WAITING 状态,表示线程的等待状态,在这种状态下,线程需要等待某个线程的特定操作才会被唤醒。我们可以使用 Object.wait()、Object.join()、LockSupport.park() 这些方法使得线程进入到 WAITING 状态,在这个状态下,必须要等待特定的方法来唤醒,比如 Object.notify 方法可以唤醒 Object.wait()方法阻塞的线程 LockSupport.unpark()可以唤醒 LockSupport.park()方法阻塞的线程。

所以,在我看来,BLOCKED 和 WAITING 两个状态最大的区别有两个:

  1. BLOCKED 是锁竞争失败后被被动触发的状态,WAITING 是人为的主动触发的状态
  2. 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/