Java 并发编程主要是通过多线程实现的,而线程的操作系统中的概念。Java 中的线程其本质上就是操作系统中的线程,但是 Java 语言对操作系统的线程做了封装。
Java 线程的生命周期,即了解线程各个节点状态的转换机制。了解 Java 线程生命周期有助于跟踪分析线程的状态,通过分析线程 dump 来解决死锁、饥饿、活锁的问题。
五态模型
- 初识状态:编程语言层面创建线程,操作系统层面未创建线程,还不允许CPU为其分配时间片。
- 可运行状态:操作系统层面创建线程,允许CPU为其分配之间片。
- 运行状态:操作系统将空闲的CPU,分配给可运行状态的线程,则线程处于运行状态。
- 休眠状态:运行状态的线程执行阻塞API(如I/O),或等待某个条件(如条件变量),则线程会释放CPU使用权,进入休眠状态。
- 终止状态:线程执行完,或者执行过程中出现异常。
Java 线程的生命周期
前面介绍了通用的线程生命周期模型——五态模型,编程语言会对操作系统对线程的操作进行封装,所以 Java 的生命周期模型和通用的线程生命周期模型有所不同,它有初识状态、运行/可运行状态、休眠状态和终止状态,其中休眠状态细分为 BLOCKED、WATING、TIMED_WATING,共 6 中状态,如下图所示:
- NEW 初始状态
- RUNNABLE 运行/可运行状态
- BLOCKED 阻塞
- WATING 无线等待
- TIMED_WATING 有时限等待
- TERMINATED 终止状态
Java 线程状态的转换
从 NEW 到 RUNNABLE
NEW 状态是在编程语言层面创建线程,而操作系统层面还没有创建线程。Java 创建线程有 3 种方式:
- 继承 Thread 类
- 实现 Runnable 接口
- 实现 Callable 接口
从 NEW 状态到 RUNNABLE 状态,就是在操作系统层面创建线程,此时需要调用 Thread.start()
方法:
MyThread myThread = new MyThread();
// 从 NEW 状态转换到 RUNNABLE 状态
myThread.start();
RUNNABLE 与 BLOCKED 状态转换
只有一种场景会触发这种转换,即线程等待 synchronized 隐式锁,此时只有一个线程能执行被 synchronized 修饰的方法或代码块,其他线程就进入 BLOCKED 状态。
当等待的线程获得 synchronized 隐式锁时,就会从 BLOCKED 状态转换为 RUNNABLE 状态。
RUNNABLE 与 WATING 状态转换
有三种场景会触发这种转换。
第一种,获取到 synchronized 隐式锁的线程调用 Object.wait()
方法,线程会从 RUNNABLE 状态转变为 WATING 状态。调用 Object.notify()
或者 Object.notifyAll()
会从 WATING 状态变为 RUNNABLE 状态。
第二种,线程调用 Thread.join()
,join()
是这一种线程同步的方法,当主线程调用 Thread A 的 A.join() 方法时,主线程就会从 RUNNABLE 转换为 WATING,当线程 A 执行完之后,又从 WATING 转换为 RUNNABLE。
第三种,调用 LockSupport.park()
当前线程会阻塞,线程的状态会从 RUNNABLE 转换到 WAITING。调用 LockSupport.unpark(Thread thread)
可唤醒目标线程,目标线程的状态又会从 WAITING 状态转换到 RUNNABLE。
RUNNABLE 与 TIMED_WATING 状态转换
- 调用带超时参数的
Thread.sleep(long millis)
方法; - 获得 synchronized 隐式锁的线程,调用带超时参数的
Object.wait(long timeout)
方法; - 调用带超时参数的
Thread.join(long millis)
方法; - 调用带超时参数的
LockSupport.parkNanos(Object blocker, long deadline)
方法; - 调用带超时参数的
LockSupport.parkUntil(long deadline)
方法。
从 RUNNABLE 到 TERMINATED
- 执行完
run()
自然退出 - 执行
run()
时抛出异常 Thread.stop()
方法,已经标记为 @DeprecatedThread.interrupt()