线程简介
进程与线程的核心区别
进程和线程是操作系统中两个核心的执行单元,二者的区别主要体现在资源分配和执行方式上:
| 对比维度 | 进程 | 线程 |
|---|---|---|
| 资源分配 | 操作系统分配资源的基本单位(拥有独立的堆、方法区等) | 进程内的执行单元,共享进程的资源(堆、方法区),但有私有栈和程序计数器 |
| 独立性 | 独立运行环境,一个进程崩溃不影响其他进程 | 依赖进程存在,同一进程内线程崩溃可能导致整个进程崩溃 |
| 调度单位 | 操作系统调度资源的单位,但调度成本高 | CPU 调度的基本单位,调度成本低(上下文切换开销小) |
| 数量关系 | 一个进程可包含多个线程 | 线程是进程的一部分,不能独立存在 |
线程的内存结构
当 JVM 创建一个线程时,会为其分配特殊的内存区域,包含以下关键部分:
- 程序计数器:记录线程下一条要执行的 JVM 字节码指令,确保线程在 CPU 时间片切换后能恢复执行(线程私有)。
- Java 栈:跟踪 Java 方法的调用关系,每个方法调用对应一个栈帧,存储局部变量、参数、返回值等(线程私有)。
- 本地方法栈:支持 native 代码的执行,与 Java 栈类似但用于本地方法调用(线程私有)。
- 线程本地存储(Thread Local):存储线程私有变量,避免多线程共享带来的并发问题(线程私有)。
- 状态管理变量:控制线程的生命周期(如状态标记、中断标志等)。
注意:线程的栈帧中,基本类型变量直接存储在栈上,而对象引用存储在栈上,对象本身存储在堆中(堆为进程共享资源)。
线程的创建方式
Java 提供了 4 种创建线程的方式,各有适用场景:
继承 Thread 类
- 重写
run()方法定义线程任务。 - 启动线程:
new ThreadSubClass().start()。 - 缺点:受限于 Java 单继承,灵活性低。
- 重写
实现 Runnable 接口
- 实现
run()方法,通过new Thread(runnable).start()启动。 - 优势:可多线程共享任务逻辑,避免单继承限制,代码与任务分离。
- 实现
实现 Callable 接口(结合 FutureTask)
1
2FutureTask<Integer> futureTask = new FutureTask<>(call);
new Thread(futureTask).start();
- 实现
call()方法(有返回值,可抛异常)。 - 通过
FutureTask包装任务,new Thread(futureTask).start()启动。 - 特点:可通过
futureTask.get()获取线程执行结果(会阻塞直到结果返回)。
使用线程池
- 通过
Executors或ThreadPoolExecutor创建线程池,复用线程资源。 - 优势:减少线程创建销毁开销,控制并发数量,适合大量短期任务。
- 通过
关键:调用
start()方法才会启动新线程,直接调用run()方法仅为普通方法调用,不会创建新线程。
线程的核心方法
线程的方法用于控制执行顺序、状态切换和资源同步,常用方法如下:
| 方法 | 作用 |
|---|---|
start() |
启动线程,JVM 会调用run()方法(不可重复调用)。 |
run() |
定义线程任务,直接调用时与普通方法无异。 |
join() |
让当前线程等待目标线程执行完毕后再继续(如t1.join()让当前线程等 t1 结束)。内部通过wait(0)实现。 |
yield() |
让当前线程让出 CPU,从运行状态转为就绪状态,重新参与 CPU 争抢(仅建议性,不一定生效)。 |
sleep(long ms) |
让线程休眠指定时间,期间不参与 CPU 调度,不释放对象锁。sleep(0)等价于yield()。 |
wait()/notify() |
用于线程间通信:wait()释放对象锁并阻塞,notify()唤醒一个等待该对象锁的线程(需在synchronized块中使用)。 |
interrupt() |
设置线程中断标志为true,若线程处于阻塞状态(如sleep、wait),会抛出InterruptedException并清除中断标志。 |
isInterrupted() |
检查线程中断标志(实例方法,不清除标志)。 |
interrupted() |
检查当前线程中断标志(静态方法,会清除标志)。 |
getState() |
获取线程当前状态(如NEW、RUNNABLE、TERMINATED等)。 |
重点方法对比
sleep() vs wait()
| 区别点 | sleep() | wait() |
| ———— | ——————————— | ——————————————— |
| 所属类 | Thread(静态方法) | Object(实例方法) |
| 锁释放 | 不释放对象锁 | 释放对象锁 |
| 使用场景 | 任意位置(需捕获异常) | 必须在synchronized块中 |
| 唤醒方式 | 时间到自动唤醒 | 需notify()/notifyAll()唤醒 |
线程状态及转换

Java 线程的生命周期包含 6 种状态,定义在Thread.State枚举中,状态转换如下:
- NEW(新建):线程已创建但未调用
start()。 - RUNNABLE(可运行):包含READY(等待 CPU 调度)和RUNNING(正在执行)两种子状态。
- 进入方式:调用
start()、yield()后重新争抢 CPU、从阻塞 / 等待状态恢复。
- 进入方式:调用
- BLOCKED(阻塞):等待获取
synchronized锁时进入。 - WAITING(无限等待):需被主动唤醒才会恢复。
- 进入方式:
Object.wait()、Thread.join()、LockSupport.park()。 - 唤醒方式:
Object.notify()、Thread执行完毕(join()场景)。
- 进入方式:
- TIMED_WAITING(计时等待):等待指定时间后自动恢复或被提前唤醒。
- 进入方式:
Thread.sleep(ms)、Object.wait(ms)、Thread.join(ms)等。
- 进入方式:
- TERMINATED(终止):线程执行完毕(
run()正常结束或异常终止)。
状态转换图:可参考原文中的示意图,核心是理解各状态间的触发条件(如锁竞争、方法调用、时间流逝等)。
其他关键概念
- 上下文切换
- 定义:CPU 从一个线程切换到另一个线程时,保存当前线程状态并加载新线程状态的过程。
- 触发时机:线程时间片用完、线程被中断、线程阻塞(如
sleep、wait)。 - 开销:包括保存 / 恢复线程状态、CPU 调度时间、CPU 缓存失效等,过多切换会影响性能。
- 线程中断
- 中断是一种协作机制,
interrupt()仅设置中断标志,线程需主动检查标志并处理(如退出执行)。 - 阻塞状态下的线程被中断会抛出
InterruptedException,需捕获并处理(通常清除标志或退出)。
- 中断是一种协作机制,
- 线程同步
- 多线程共享资源时需保证原子性、可见性和有序性,常用手段:
synchronized关键字、Lock接口、volatile关键字等
- 多线程共享资源时需保证原子性、可见性和有序性,常用手段: