并发不一定需要依赖多线程,但是在 Java 领域,实现并发程序的主要手段就是多线程。
线程的实现
线程是比进程更轻量级的调度执行单位,线程的引入,可以把一个进程的资源分配和执行调度分开,各个线程既可以共享进程资源(内存地址、文件 I/ O 等),又可以独立调度。目前,线程是Java里进行处理器资源调度的最基本单位。
实现线程主要有三种方式:使用内核线程实现(1: 1 实现),使用用户线程实现(1: N 实现),使用用户线程加轻量级进程混合实现(N: M 实现)。Java中的线程实现在JDK1.2之前是使用的“绿色线程”,可以看作是用户线程的实现,它的好处在于,不需要系统内核的支持,缺点也在于没有系统内核的支持,如果进程中有某个线程进行了某些耗时长的操作,会阻塞整个进程。从JDK1.3开始,Java线程的实现采用的是LWP(轻量级进程),即内核线程实现(1: 1 实现)。线程的调度交给操作系统,但是这种方式的缺点在于线程消耗资源太大了,例如在linux上,一个线程默认的栈大小是1M,单机创建几万个线程就有点吃力了。所以后来在编程语言的层面上,就出现了协程这个东西。
Java 中线程的生命周期
Java语言定义了6种线程状态:
- NEW(初始化状态)
- RUNNABLE(可运行 / 运行状态)
- BLOCKED(阻塞状态)
- WAITING(无时限等待)
- TIMED_WAITING(有时限等待)
- TERMINATED(终止状态)
但其实在操作系统层面,Java 线程中的 BLOCKED、WAITING、TIMED_WAITING 是一种状态即休眠状态,也就是说只要 Java 线程处于这三种状态之一,那么这个线程就永远没有 CPU 的使用权。
Java线程的各种状态转化如下图:
注意:
上图中是有一处错误的!从RUNNABLE到BLOCKED的转换只有一种可能,就是线程等待 synchronized 的隐式锁。线程调用阻塞式 API,比如以阻塞方式读文件的时候,在操作系统层面,线程是会转换到休眠状态的,但是在 JVM 层面,Java 线程的状态不会发生变化,也就是说 Java 线程的状态会依然保持 RUNNABLE 状态。
我们平时所谓的 Java 在调用阻塞式 API 时,线程会阻塞,指的是操作系统线程的状态,并不是 Java 线程的状态。
参考
- 周志明. 深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)
- 阿里面试官问我Java线程和操作系统线程什么关系