Thread
线程是操作系统的概念,不同的开发语言都对齐进行了封装,Java 语言里的线程本质上就是操作系统的线程,它们是一一对应的。
通用的线程生命周期有五种状态:初始状态、可运行状态、运行状态、休眠状态和终止状态。

- 初始状态:指的是线程已经被创建,但是还不允许分配 CPU 执行。这个状态属于编程语言特有的,这里的创建仅仅在编程语言中创建,但是没有在操作系统层面创建。
- 可运行状态:线程可以分配 CPU 执行,真正的操作系统线程已经被创建了。
- 运行状态:被分配到 CPU 的线程的状态。
- 休眠状态:运行状态的线程如果调用一个阻塞的 API(例如以阻塞方式读文件)或者等待某个事件(例如条件变量),那么线程的状态就会转换到此状态,同时释放 CPU 使用权,休眠状态的线程永远没有机会获得 CPU 使用权。
- 终止状态:线程执行完或者出现异常。
这五种状态在不同的编程语言里面会有简化,如C 语言的 POSIX Threads 规范,就把初始状态和可运行状态合并了;Java 语言里则把可运行状态和运行状态合并了。
除了合并,也会有细化,Java 语言里就细化了休眠状态。
java.lang.Thread.State
定义了 Java 线程的6种状态。public class Thread implements Runnable {
/**
* These states are virtual machine states which do not reflect
* any operating system thread states.
*/
public enum State {
NEW,
/**
* A thread in the runnable
* state is executing in the Java virtual machine but it may
* be waiting for other resources from the operating system
* such as processor.
*/
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
}
- NEW:new 了一个 Thread 对象,但未调用
start()
方法。 - RUNNABLE:调用了 Thread 的
start()
方法后进入的状态,可以被 CPU 调度。 - BLOCKED:线程阻塞在进入
synchronized
关键字修饰的方法或代码块时的状态。 - WAITING:不会被分配 CPU 执行时间,它们要等待被显式地唤醒,否则会处于无限期等待的状态。
- TIMED_WAITING:不会被分配 CPU 执行时间,需要被显示地唤醒,或达到一定时间后自动唤醒。
- TERMINATED:线程已经执行完毕,不可逆转。若再调用
start()
方法,会抛出java.lang.IllegalThreadStateException
异常。
- 1.有人觉得 Java 线程状态中还少了个 running 状态,但这其实是把两个不同层面的状态混淆了。
- 2.Java 线程没有 running 的概念,它的 RUNNABLE 包含了 操作系统的 ready 和 running 两种状态。
- 3.注意看 State 的注释,Java 线程状态是虚拟机层面的,不会反映操作系统的线程状态。
- 4.Java 不需要 running 状态是因为没有必要去区分,操作系统的 CPU 时间片段一般很小,10-20ms 左右,Java 去区分是没有意义 的。
BLOCKED、WAITING、TIMED_WAITING 可以理解为线程导致休眠状态的三种原因。

线程状态的变化
线程调用阻塞式 API 时,不会进入 BLOCKED 状态,在操作系统层面是休眠状态,但是在 JVM 层面还是 RUNNABLE 状态。因为在 JVM 看来,等待 CPU 使用权(操作系统层面此时处于可执行状态)与等待 I/O(操作系统层面此时处于休眠状态)没有区别,都是在等待某个资源,所以都归入了 RUNNABLE 状态。
每个对象锁都对应一个等待队列和一个同步队列,详情见下一节。下图是一个线程状态转换的例子。

等待队列与同步队列
- 1.
Thread.sleep(long millis)
:一定是当前线程调用此方法,当前线程进入 TIMED_WAITING 状态,但不释放对象锁,millis 后线程自动苏醒进入就绪状态。是给其它线程执行机会的最佳方式。 - 2.
Thread.yield()
:一定是当前线程调用此方法,当前线程放弃获取的 CPU 时间片,但不释放锁资源,由运行状态变为就绪状态,让 OS 再次选择线程。作用:让相同优先级的线程轮流执行,但并不保证一定会轮流执行。实际中无法保证 yield() 达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。Thread.yield()
不会导致阻塞。该方法与sleep()
类似,只是不能由用户指定暂停多长时间。 - 3.
thread.join()
:当前线程里调用其它线程 t 的join()
方法,当前线程进入 WAITING/TIMED_WAITING 状态,当前线程不释放已经持有的对象锁。线程 t 执行完毕或者 millis 时间到,当前线程一般情况下进入 RUNNABLE 状态,也有可能进入 BLOCKED 状态(因为join()
是基于wait()
实现的)。 - 4.
thread.interrupt()
:- 若线程 A 处于 WAITING 或 TIMED_WAITING 状态时,线程 B 调用线程 A 的 interrupt 方法,则会使线程 A 进入 RUNNABLE 状态,触发 InterruptedException;
- 若线程 A 处于 RUNNABLE 状态,并阻塞在
java.nio.channels.InterruptibleChannel
上时,调用线程 A 的 interrupt 方法,会触发java.nio.channels.ClosedByInterruptException
; - 若线程 A 处于 RUNNABLE 状态,阻塞在
java.nio.channels.Selector
上时,调用线程 A 的 interrupt 方法,线程 A 会立即返回; - 若线程 A 处于 RUNNABLE 状态,并没有阻塞在任何 I/O 操作上,调用线程 A 的 interrupt 方法,线程 A 可以通过主动监测的方式(isInterrupted)判断自己是否已经被中断;也可忽略中断。
- 5.
obj.wait()
:当前线程调用对象的wait()
方法,当前线程释放对象锁,进入等待队列。依靠notify()/notifyAll()
唤醒或者wait(long timeout)
timeout 时间到自动唤醒。 - 6.
obj.notify()
:唤醒在此对象监视器上等待的单个线程,选择是任意性的。notifyAll()
唤醒在此对象监视器上等待的所有线程。 - 7.
LockSupport.park(), LockSupport.parkUntil(long deadlines)
:当前线程进入 WAITING/TIMED_WAITING 状态。对比wait()
方法,不需要获得锁就可以让线程进入 WAITING/TIMED_WAITING状态,需要通过LockSupport.unpark(Thread thread)
唤醒。
join()
的本质是先调用synchronized
方法获取线程的锁,然后调用wait()
方法,当线程 terminate 的时候,会调用notifyAll()