Java 线程
- 线程是比进程更轻量级的调度执行单位,CPU 调度的基本单位就是线程。
- 线程的引入,将一个进程的资源分配和执行调度分开。
- 各个线程既可以共享进程资源(内存地址、文件 I/O 等),又可独立调度。
Java 线程学习地图
Java 线程:概念篇
线程的生命周期状态
想要了解线程,我们可以先从它的程(人)生轨迹入手,也就是它的生命周期,线程是操作系统中的一个重要概念,而 Java 线程本质上只是对操作系统的线程进行了封装,简单打个比方就是,Java 线程相当于
所以我们先来看看通用的线程生命周期是什么样的,再来看看 Java 都对其进行了怎样的封装。
通用的线程生命周期
首先,通用的线程生命周期模型将线程的状态分为了以下五种:
- 初始状态:
- 线程仅仅在编程语言层面被创建,在操作系统中并没有被创建,因此还不能被分配 CPU 资源;
- 相当于现在只是在 Java 中 new 了个
Thread
对象,还没调用start()
方法。
- 可运行状态:
- 真正的操作系统线程此时已经成功被创建,线程已经可以被分配 CPU 资源了。
- 运行状态:
- 当有空闲的 CPU 资源时,操作系统会将其分配给一个处于可运行状态的线程,可运行状态的线程一旦被分配的 CPU,它的状态将变为运行状态。
- 休眠状态:
- 运行状态的线程如果调用了某个阻塞式 API(如以阻塞方式读文件),那么这个线程将变为休眠状态,并放弃自己的 CPU 使用权;
- 当它的阻塞状态结束了,它的状态会变为可运行状态,等待再次被分配 CPU 资源。
- 终止状态:
- 当线程执行完或出现异常时,它就会进入终止状态,这是一个终态(只进不出的饕餮状态),就是挂了。
它们之间的状态流转关系为:
Java 中线程的生命周期
对于 Java 线程而言,它对操作系统的线程状态划分略有不满。
首先,它觉得可运行状态和运行状态还搞两个状态有点麻烦,这两个状态只对操作系统调度层面有用,而 Java 秉承不重复造轮子的原则,将线程的调度交给操作系统处理了。所以,它把这两个状态合并成一个状态,即 RUNNABLE。
接下来,它又嫌弃休眠状态的分类太过笼统,所以它又细化了休眠状态,根据休眠的原因,将其细分为了:BLOCKED、WAITING、TIME_WAITING 状态。
最后,Java 将线程的生命周期状态改成了如下形式:
接下来,我们将这些状态之间的流转规则进行探究,主要有以下三条链路:
RUNNABLE -> BLOCKED/WAITING/TIME_WAITING
NEW -> RUNNABLE
RUNNABLE -> TERMINATED
线程的生命周期状态转换
可运行/运行状态 -> 休眠状态
RUNNABLE -> BLOCKED
:- 线程等待 synchronized 的隐式锁时,触发该状态转换。
RUNNABLE -> WAITING
:- 有三种场景会触发这种转换:
- 已获取 synchronized 隐式锁的线程,调用无参数的
Object.wait()
方法。 - 调用无参数的
Thread.join()
方法。- 因为
Thread.join()
其实是通过调用线程对象本身的wait(0)
方法实现的。
- 因为
- 调用
LockSupport.park()
方法。
- 已获取 synchronized 隐式锁的线程,调用无参数的
- 有三种场景会触发这种转换:
RUNNABLE -> TIMED_WAITING
:- 有五种场景会触发这种转换:
- 调用带超时参数的
Thread.sleep(long millis)
方法; - 获得 synchronized 隐式锁的线程,调用带超时参数的
Object.wait(long timeout)
方法; - 调用带超时参数的
Thread.join(long millis)
方法; - 调用带超时参数的
LockSupport.parkNanos(Object blocker, long deadline)
方法; - 调用带超时参数的
LockSupport.parkUntil(long deadline)
方法。
- 调用带超时参数的
- 有五种场景会触发这种转换:
LockSupport 对象说明:
Java 并发包中的锁,都是基于该对象实现的,其使用方法如下:
- 调用
LockSupport.park()
方法,当前线程会阻塞,线程的状态会从 RUNNABLE 转换到 WAITING。- 调用
LockSupport.park()
方法,当前线程会阻塞,线程的状态会从 RUNNABLE 转换到 WAITING。
初始状态 -> 可运行/运行状态
Java 刚创建出来的 Thread thread
对象就是 NEW 状态,调用了 thread.start()
方法后,线程就进入了 RUNNABLE 状态。
可运行/运行状态 -> 中止状态
- 线程执行完
run()
方法后,会自动转换到 TERMINATED 状态。 - 线程在执行
run()
方法的时,有异常抛出,线程也会被终止。
如何强制中断
run()
方法的执行?当
run()
方法中调用了一个耗时很长的方法时,我们等的不耐烦了,此时我们需要强制中断run()
方法的执行。在 Java 的 Thread 中,倒是给我们提供了一个
stop()
方法,不过该方法已经被标记为@Deprecated
的了,官方已经不推荐我们使用它了。不推荐它的原因是它太危险了,
stop()
方法不会给线程任何处理后事的机会,直接就杀掉线程,如果此时线程正好持有 ReentrantLock 锁,它被干掉后会导致这个锁永远不会被释放,这实在是太危险了。一种比较优雅的取消线程的方式是 中断,即调用
interrupt()
方法,这个方法并没做什么实质上的事情,它相当于只是给线程打上了一个标记,而后我们可以通过一些手段(如调用Thread.interrupted()
方法)来检测当前线程是否被打上了中断标记,来决定如何终止线程。if (Thread.interrupted()) { // Clears interrupted status! // do something like lock.unlock() throw new InterruptedException(); }