副本 Java 线程基础
一、操作系统底层前置知识
1. 进程 vs 线程
进程
程序运行实例,操作系统资源分配最小单位(内存、文件、网络),进程之间完全隔离。进程间通信(IPC)成本高:管道、信号、消息队列、共享内存、信号量、Socket。
线程
进程内指令流,CPU 调度最小单位,又叫轻量级进程 LWP;同进程内所有线程共享进程堆、全局变量,仅私有栈、寄存器;线程切换开销远小于进程。
核心区别
-
资源:进程独立内存,线程共享进程资源
-
切换成本:进程切换极高,线程切换轻量
-
通信:进程 IPC 复杂;线程直接读写共享变量
-
调度:操作系统调度线程
2. 上下文切换 Context Switch
定义
CPU 暂停当前线程,保存寄存器 / 程序计数器,加载下一线程上下文,恢复运行。只能发生内核态,是系统高开销操作,频繁切换会严重降低性能。

两种切换
-
主动切换 cswch:线程主动放弃 CPU(sleep、wait、park、yield)
-
被动切换 nvcswch:时间片耗尽、更高优先级线程抢占
Linux 查看命令
# 全局每秒切换统计
vmstat 1
# 单个进程上下文切换
pidstat -w -p 进程PID 1
# 进程总切换次数
cat /proc/PID/status
3. OS 五态线程模型
操作系统原生线程 5 种状态:初始→就绪→运行→休眠→终止

-
初始:创建完毕,未申请内核线程
-
就绪:内核线程创建完成,等待 CPU 时间片
-
运行:分配 CPU 执行指令
-
休眠:阻塞 / 等待事件,释放 CPU
-
终止:执行完毕,资源回收
4. Linux 线程实现:NPTL
Java 线程基于 Linux NPTL(原生 POSIX 线程),一对一模型:1 个 Java 线程映射 1 个 OS 轻量级进程。
查看系统线程库:getconf GNU_LIBPTHREAD_VERSION
5. 协程 Coroutine(对比 Java 线程)

-
调度:线程由 OS 内核调度;协程用户态自主调度,无内核上下文切换
-
内存:线程栈默认 1M;协程栈 KB 级,可大量创建
-
锁:单协程无并发竞争,无需锁;多线程共享变量必须同步
-
适用:协程适合 IO 密集;线程适合 CPU 密集
-
Java 原生无协程,第三方框架:Quasar、Kilim
二、Java 线程基础核心
1. Java 创建线程 4 种写法(本质只有 1 种)
底层全部依赖new Thread().start()创建内核线程,Runnable/Callable 只是任务载体。
方式 1:继承 Thread 类
Thread t = new Thread(){
@Override
public void run() {
System.out.println("子线程执行");
}
};
t.start();
方式 2:实现 Runnable(解耦线程与任务,推荐)
Runnable task = () -> System.out.println("任务执行");
Thread t = new Thread(task);
t.start();
方式 3:Callable+Future(带返回值,配合线程池)
class MyTask implements Callable<Integer> {
@Override
public Integer call() {
return 100;
}
}
ExecutorService pool = Executors.newSingleThreadExecutor();
Future<Integer> future = pool.submit(new MyTask());
Integer res = future.get();
方式 4:Lambda 简写
new Thread(() -> System.out.println("lambda线程")).start();
关键面试题:为什么不能直接调用 run ()?
run()只是普通方法,不会创建 OS 线程,仅当前主线程串行执行;
start()调用 native 方法,向操作系统申请内核线程,就绪后由 CPU 调度执行 run。

2. Java 线程调度机制:抢占式
-
协同调度:线程主动让出 CPU,缺陷:单个死循环线程卡死整个程序
-
抢占调度(Java 采用):OS 分配时间片,时间片耗尽强制切换;高优先级线程可抢占
-
优先级:1~10,默认 5;优先级仅给 OS 参考,不保证绝对执行顺序,受底层系统限制。
3. Java 线程 6 种状态(jstack 标准)

| 状态 | 含义 | 触发场景 |
|---|---|---|
| NEW | 新建 | new Thread (),未调用 start () |
| RUNNABLE | 就绪 + 运行合并 | start () 后;OS 就绪 / 运行都归为此状态 |
| BLOCKED | 阻塞 | 竞争 synchronized 锁失败,等待 monitor |
| WAITING | 无限等待 | wait()、join()、LockSupport.park() |
| TIMED_WAITING | 限时等待 | sleep(n)、wait(n)、parkNanos |
| TERMINATED | 终止 | run 执行完毕 / 异常退出 |
区分:OS 休眠状态 = Java BLOCKED+WAITING+TIMED_WAITING,这三类都无 CPU 时间片。
三、Thread 核心 API 详解
1. sleep(long ms)
-
进入 TIMED_WAITING,不释放对象锁
-
可被 interrupt 打断,抛出
InterruptedException,同时清空中断标记 -
sleep (0) 等价 yield ()
2. yield()
-
让出 CPU,回到 RUNNABLE 就绪状态,不释放锁
-
仅提示调度器,不保证一定切换线程
3. join ():线程等待合并
主线程阻塞,等待目标线程执行完毕再继续;底层基于 wait/notify 实现。
Thread t = new Thread(() -> Thread.sleep(5000));
t.start();
t.join(); // 主线程阻塞5秒
System.out.println("子线程执行完");
4. stop () 废弃方法(禁止使用)
暴力终止线程,强行释放锁,导致共享数据半写、数据不一致,并发安全灾难。
四、线程中断机制(优雅终止线程核心)
核心思想:协作式中断
没有强制杀死线程 API,仅设置中断标记,由线程自身判断标记、自主退出。
三个核心方法
-
t.interrupt():设置线程中断标记 = true,不会停止线程 -
t.isInterrupted():获取中断标记,不清空标记 -
Thread.interrupted():静态方法,返回标记并重置为 false
标准优雅停止模板
public class StopTask implements Runnable {
@Override
public void run() {
// 循环判断中断标记
while (!Thread.currentThread().isInterrupted()) {
// 执行业务
}
System.out.println("线程正常退出");
}
}
阻塞方法的中断特性
sleep() / wait() / join() / park() 阻塞时收到中断:
-
抛出
InterruptedException -
自动清除中断标记
避坑写法(捕获异常后恢复中断)
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// 重新打上中断标记,上层可感知
Thread.currentThread().interrupt();
break;
}
五、Java 线程间通信 4 种方式
1. volatile 共享变量(可见性通信)
多线程感知变量修改,依托 JMM 可见性,适合简单开关控制:
private static volatile boolean flag = true;
2. wait/notify 等待唤醒(synchronized 配套)
-
必须在同步块内调用,持有对象 monitor 锁
-
wait:释放锁,进入 WAITING;notify/notifyAll 唤醒等待队列线程
-
标准生产者消费者基础方案
3. LockSupport park/unpark(无锁阻塞,AQS 底层依赖)
优势:无需同步块,可指定线程唤醒、不受调用顺序限制;多次 unpark 等价一次许可。
Thread parkT = new Thread(() -> {
System.out.println("等待许可");
LockSupport.park();
System.out.println("被唤醒");
});
parkT.start();
LockSupport.unpark(parkT);
4. Thread.join 线程等待通信
通过等待其他线程完成,保证执行顺序。
5. 管道流 PipedInputStream/PipedWriter
内存管道,线程间直接传输字节 / 字符,适合数据传递。
六、高频面试简答题汇总
-
创建线程有几种方式?本质是什么?答:4 种写法,底层全部依靠 Thread.start () 创建操作系统内核线程。
-
start () 和 run () 区别?答:run 是普通方法;start 申请 OS 线程,调度后执行 run。
-
线程 6 种状态,BLOCKED 和 WAITING 区别?答:BLOCKED 是等 synchronized 锁;WAITING 是 wait/park 无时限等待。
-
为什么不用 stop () 停止线程?答:强制释放锁,造成共享数据损坏,并发数据不一致。
-
interrupt 中断原理,sleep 被中断会发生什么?答:仅打标记;sleep 中断抛异常并清除标记,需要手动恢复中断位。
-
yield、sleep、join 区别?
-
yield:让出 CPU,不释放锁,RUNNABLE;
-
sleep:限时阻塞,不释放锁;
-
join:等待其他线程完成,底层 wait。
-
进程和线程区别?上下文切换代价谁更大?答:进程资源隔离,线程共享进程资源;进程切换开销远大于线程。
-
Java 线程是用户级还是内核级?答:一对一内核级线程,每条 Java 线程对应一个 OS 轻量级进程。
-
LockSupport 相比 wait/notify 优势?无需同步锁、可指定唤醒线程、park/unpark 不分先后顺序。

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐


所有评论(0)