一、线程的状态简介
JDK 的线程状态枚举类 java.lang.Thread.State
,定义了线程的6种状态:
-
NEW
: 新建线程,尚未调用thread.start()
-
RUNNABLE
: 可在 JVM 中执行,需等待CPU资源 -
BLOCKED
: 调用了Synchronized
方法或语句块,等待对象锁资源 -
WAITING
: 调用了Object#wait(), Thread#join(), LockSupport#park()
等方法中的一个,等待其他线程执行特定的动作 (notify
或 线程终止) -
TIMED_WAITING
: 调用了Thread#sleep(long), Object#wait(long), Thread#join(long), LockSupport#parkNanos, LockSupport#parkUntil
等方法中的一个 -
TERMINATED
: 线程执行完毕
二、线程的状态转换
注:上面列出的为线程的正常状态流转,可调用 Thread#interrupt()
中断。
RUNNABLE
RUNNABLE
状态表示该线程已就绪,可运行,但不一定能立刻执行,还需等待CPU
调度 。
BLOCKED
- 线程进入
Synchronized
方法或语句块; - 线程调用
Object#wait
方法被Object#notify/notifyAll
唤醒后,变为RUNNABLE
状态,并重新竞争monitor
锁;
注:线程需要先竞争 monitor
锁成功,再等待 CPU
调度。
WAITING
线程调用 Object#wait(), Thread#join(), LockSupport#park()
等方法中的一个,进入 WAITING
状态。需要其他线程调用 Object#notity() 或 Object#notifyAll()
方法唤醒线程,否则一直等待。
TIMED_WAITING
线程调用 Thread#sleep(long), Object#wait(long), Thread#join(long), LockSupport#parkNanos, LockSupport#parkUntil
等方法中的一个,进入 TIMED_WAITING
状态。其他线程调用 Object#notity(), Object#notifyAll()
或超时时间到达,唤醒线程。
三、monitor 和 wait set
在 Java Language Specification 的描述中,Java 每个对象都有一个关联的 monitor
(监视器) 和 wait set
(等待队列),通过 Synchronized
和 wait/notify
进行转换:
- 线程通过
Synchronized
获取monitor
锁,多线程时,未获取到锁的线程进入Entry Set
等待; - 已获取
monitor
锁的线程,通过调用Object#wait
方法,进入Wait Set
等待区,并释放monitor
锁; - 其他线程通过
Object#notify, Object#notifyAll
方式唤醒Wait Set
区内的线程,竞争 对象Monitor
锁。
图示如下:
注意1:在调用 Object#wait, Object#notify, Object#notifyAll
等方法之前,必须先获得该对象的 monitor
。
注意2: 通过调用 Synchronized
获取对象的 monitor
锁的方式有:
class Ex {
private Object L = new Object();
// 锁对象是Ex.class
public static synchronized A() { ... }
// 锁对象是当前对象(this)
public synchronized A() { ... }
public A() {
// 锁对象是L
synchronized(L) {
...
}
}
}
四、方法说明
Thread#sleep(long)
调用后进入 TIMED_WAITING
状态,使当前线程休眠一定时间,不会释放对象锁
,时间到了,则线程进入 RUNNABLE
状态。
Object#wait()/Object#wait(long)
调用后线程状态变为 WAITING/TIMED_WAITING
,进入等待队列 (wait set
),并释放监视器锁
。
注:当前线程必须先获得监视器锁,否则抛 IllegalMonitorStateException
异常。
Thread#join()
等待当前线程消亡,同步方法。内部通过判断线程 isAlive()
状态,循环调用 Object#wait(long)
。
Object#notify()/notifyAll()
notify
: 唤醒一个等待队列中的线程,如果有多个线程,则由 JVM 任意挑选一个。notifyAll
: 唤醒所有等待队列中的线程。
其他线程调用 Object#notify()/notifyAll()
后,当前线程变为 RUNNABLE
,并重新竞争 monitor
锁,竞争成功状态变为 RUNNABLE
,竞争失败变为 BLOCKED
。
调用 notify/notifyAll
后,调用线程并不一定立即释放 monitor
锁,需等待对应逻辑执行完,即线程为 terminated
或被中断,才会释放。
注:当前线程必须先获得监视器锁,否则抛 IllegalMonitorStateException
异常。
Thread#yield()
提示 CPU
可以释放当前线程占用的 CPU
资源,防止 CPU
过度使用,但 CPU
可以忽略这个提示。
让出 CPU
资源后,当前线程变为 RUNNABLE
状态,继续和其他线程竞争 CPU
资源。
Thread#interrupt()
可中断正在运行的线程。需要注意的点有:
- 如果中断的不是正在运行的线程,会调用
java.lang.Thread#checkAccess
方法,可能抛出java.lang.SecurityException
异常 - 如果线程阻塞在
wait, join, sleep
等方法时,中断状态会被清除,并抛出java.lang.InterruptedException
异常 - 如果线程阻塞在使用
java.nio.channels.InterruptibleChannel
的 I/O 操作,会关闭管道,设置中断状态,抛出java.nio.channels.ClosedByInterruptException
异常 - 如果线程阻塞在
java.nio.channels.Selector
,设置中断状态,并立即返回,和调用Selector#wakeup()
方法一样 - 如果不是以上列出的情形,则设置中断状态
- 不会对已终结的线程造成影响
五、如何查看线程状态
使用 jstack <pid>
命令,可以查看进程的线程信息。
RUNNABLE
运行中:
等待资源:
WAITING
调用 Object.wait()
:
调用 LockSupport#park()
:
TIMED_WAITING
调用 Object#wait(long)
:
调用 Thread#sleep(long)
:
调用 LockSupport#parkNanos(long)
:
参考资料
java.lang.Thread.StateJava Language SpecificationJava线程同步机制Monitors – The Basic Idea of Java Synchronization