Java 线程模型

160 阅读3分钟

1. Java 线程模型

Java 线程和操作系统内核的线程是一对一的,在 Java 程序中每创建一个新线程,JVM 都会通过系统调用创建一个新的操作系统用户线程,Java 线程对象会绑定在这个 OS 线程上;虽然 Java 线程和 OS 线程是一对一,但 Java 并没有只是简单的包装 OS 线程模型,而是定义了自己的线程模型。

2. OS 线程的状态

操作系统线程的状态一般有5种:创建(create)、就绪(ready)、运行(running)、阻塞(blocked)、终止(terminated);其中:

  1. 就绪状态:表示用户线程已经准备就绪,可以进行调度,目前在队列中等待调度获取 CPU 时间片进行运行;
  2. 运行状态:表示用户线程已被调度,获取了 CPU 时间片,正在执行;
  3. 等待状态:表示用户线程未就绪,正在等待其他临界资源,这种状态的用户线程 CPU 不会调度;

3. Java 线程的状态

Java 线程状态有6种:创建(NEW)、可运行(RUNNABLE)、阻塞(BLOCKED)、等待(WAITING)、定时等待(TIMED_WAITING)、TERMINATED(终止状态); Java 线程状态与 OS 线程的状态并不一致,其中 OS 线程状态的 ready 和 running 被 Java 线程合并成了 RUNNABLE,无论是等待调度还是已调度对 Java 线程来说都是可运行状态; 而 OS 线程的 blocked 状态被 Java 线程分为了阻塞(BLOCKED)、等待(WAITING)、定时等待(TIMED_WAITING) 3 种;

  • BLOCKED 状态:Java 线程和 OS 线程是1对1的关系,如果 Java 线程是被动阻塞,一般是被内核挂起,则会进入BLOCKED 状态,场景通常是因为竞争 JVM 层的 monitor 锁失败,线程被挂起;

  • WAITING 状态:Java 线程进入等待状态,一般是通过 Object.wait 或者 LockSupport.park 操作,Thread.join() 本质也是 Object.wait 操作,是 Java 程序主动将自己挂起的操作,则进入 WAITING 状态;

Java 中的两种锁,一种是内部锁,也就是 JVM 的监视器锁(monitor),该锁是在 JVM 层实现的,底层使用操作系统的 MUTEX 实现,在 Java 中使用 synchronized 关键字来使用,当 Java 代码使用了 synchronized 来加锁,实际上将加锁操作委托给了 JVM,而 JVM 又通过系统的 MUTEX 来加锁,会发生系统调用,一旦失败,线程会被阻塞,这种阻塞完全是由 JVM 和 OS 控制的,对于JAVA 程序来说是被动的,因此是 BLOCKED 状态。另一种锁是 JUC 下面的锁机制,JUC 内的锁是在 JDK 层面实现的锁,其底层使用基于 CAS 操作的原子变量来实现,不会进行系统调用,因此 JUC 内的锁一般采取混合锁机制,当使用 CAS 操作加锁失败,会进行自旋重试,如果多次重试仍然失败,会主动将自己挂起,主要是使用 LockSupport 的 park 操作来执行,这种主动将线程挂起的操作是由 Java 程序自身进行控制的,这种主动挂起操作的状态就是 WAITING 状态;

图1. Java 线程的状态转换模型

4. Java 线程调度

一般线程调度算法有公平式调度算法和抢占式调度算法,Linux 内核就是可抢占式任务调度策略;JVM 中的线程调度是可抢占式的,所以 Java 中的线程模型,也就是 Thread 这个类可以设置线程优先级,优先级越高,线程调度器会分配更多的 CPU 时间片