第1部分-并发编程基础与线程模型
第1部分:并发编程基础与线程模型
核心目标
理解并发的概念、Java的线程机制与底层原理。
1. 并发 vs 并行
并发(Concurrency)
- 定义:多个任务在同一个时间段内交替执行,但在任意时刻只有一个任务在执行
- 特点:通过时间片轮转实现多任务"同时"执行
- 适用场景:I/O密集型任务,如网络请求、文件读写
并行(Parallelism)
- 定义:多个任务在同一时刻真正同时执行
- 特点:需要多核CPU支持,任务真正并行运行
- 适用场景:CPU密集型任务,如数学计算、图像处理
对比示例
// 并发示例:单核CPU上的多线程
public class ConcurrencyExample {public static void main(String[] args) {// 两个线程在单核上交替执行new Thread(() -> System.out.println("任务1")).start();new Thread(() -> System.out.println("任务2")).start();}
}// 并行示例:多核CPU上的多线程
public class ParallelismExample {public static void main(String[] args) {// 两个线程在多核上真正同时执行IntStream.range(0, 2).parallel().forEach(i -> System.out.println("并行任务" + i));}
}
2. 进程与线程的区别
进程(Process)
- 定义:操作系统进行资源分配和调度的基本单位
- 特点:
- 拥有独立的内存空间
- 进程间通信需要特殊机制(IPC)
- 创建和销毁开销大
- 进程间相互独立,一个进程崩溃不影响其他进程
线程(Thread)
- 定义:CPU调度和执行的基本单位,是进程内的执行单元
- 特点:
- 共享进程的内存空间
- 线程间通信简单(共享变量)
- 创建和销毁开销小
- 一个线程崩溃可能影响整个进程
对比表
特性 | 进程 | 线程 |
---|---|---|
内存空间 | 独立 | 共享 |
通信方式 | IPC | 共享变量 |
创建开销 | 大 | 小 |
切换开销 | 大 | 小 |
安全性 | 高 | 低 |
3. Java线程生命周期与状态
线程状态枚举(Thread.State)
public enum State {NEW, // 新建状态RUNNABLE, // 可运行状态BLOCKED, // 阻塞状态WAITING, // 等待状态TIMED_WAITING, // 超时等待状态TERMINATED // 终止状态
}
状态详解
NEW(新建状态)
- 线程对象创建后,调用
start()
方法之前 - 此时线程还未开始执行
RUNNABLE(可运行状态)
- 调用
start()
方法后,线程进入此状态 - 包括正在运行和等待CPU调度两种情况
- 在Java中,阻塞I/O不会改变线程状态为BLOCKED
BLOCKED(阻塞状态)
- 线程等待获取同步锁时进入此状态
- 当锁被释放时,线程重新进入RUNNABLE状态
WAITING(等待状态)
- 调用
wait()
、join()
、LockSupport.park()
等方法 - 需要其他线程调用
notify()
、notifyAll()
等方法唤醒
TIMED_WAITING(超时等待状态)
- 调用
sleep()
、wait(timeout)
、join(timeout)
等方法 - 在指定时间后自动唤醒
TERMINATED(终止状态)
- 线程执行完毕或异常退出
- 无法再次启动
状态转换图
NEW → RUNNABLE → BLOCKED → RUNNABLE↓ ↓↓ WAITING ← RUNNABLE↓ ↓↓ TIMED_WAITING ← RUNNABLE↓ ↓↓ TERMINATED ← RUNNABLE
4. Java内存模型(JMM)简介
三大特性
原子性(Atomicity)
- 定义:一个操作不可被中断,要么全部执行,要么全部不执行
- 保证方式:synchronized、Lock、原子类
- 示例:
// 非原子操作
int i = 0;
i++; // 实际包含:读取i、计算i+1、写入i// 原子操作
AtomicInteger atomicI = new AtomicInteger(0);
atomicI.incrementAndGet(); // 原子操作
可见性(Visibility)
- 定义:一个线程对共享变量的修改,其他线程能够立即看到
- 问题原因:CPU缓存、指令重排序
- 保证方式:volatile、synchronized、Lock、final
- 示例:
// 可见性问题
class VisibilityProblem {private boolean flag = false; // 可能不可见public void setFlag() {flag = true; // 其他线程可能看不到}public void checkFlag() {while (!flag) {// 可能永远循环}}
}// 解决方案
class VisibilitySolution {private volatile boolean flag = false; // volatile保证可见性public void setFlag() {flag = true; // 其他线程能立即看到}public void checkFlag() {while (!flag) {// 能正常退出循环}}
}
有序性(Ordering)
- 定义:程序执行的顺序按照代码的先后顺序执行
- 问题原因:指令重排序优化
- 保证方式:volatile、synchronized、Lock、happens-before规则
JMM结构
┌─────────────────────────────────────────┐
│ 主内存 (Main Memory) │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ 变量A │ │ 变量B │ │ 变量C │ │
│ └─────────┘ └─────────┘ └─────────┘ │
└─────────────────────────────────────────┘↑ ↑│ │┌─────────────┐ ┌─────────────┐│ 线程1工作内存 │ │ 线程2工作内存 ││ ┌─────────┐ │ │ ┌─────────┐ ││ │ 变量A副本│ │ │ │ 变量A副本│ ││ └─────────┘ │ │ └─────────┘ │└─────────────┘ └─────────────┘
5. 上下文切换与线程调度机制
上下文切换(Context Switch)
- 定义:CPU从一个线程切换到另一个线程时,需要保存和恢复线程的状态信息
- 开销:包括保存/恢复寄存器、程序计数器、栈指针等
- 优化:减少线程数量、使用线程池、避免不必要的同步
线程调度机制
抢占式调度(Preemptive Scheduling)
- 特点:操作系统决定何时切换线程
- 优点:响应性好,防止单个线程独占CPU
- 缺点:切换开销大
协作式调度(Cooperative Scheduling)
- 特点:线程主动让出CPU控制权
- 优点:切换开销小,可预测性好
- 缺点:可能导致线程饥饿
Java线程调度
// 线程优先级设置(1-10,默认为5)
Thread thread = new Thread(() -> {// 线程任务
});
thread.setPriority(Thread.MAX_PRIORITY); // 10
thread.setPriority(Thread.MIN_PRIORITY); // 1
thread.setPriority(Thread.NORM_PRIORITY); // 5
6. Java 21中的线程模型演进
传统线程模型
- 特点:1:1映射(一个Java线程对应一个操作系统线程)
- 问题:
- 创建线程开销大(约2MB栈空间)
- 上下文切换成本高
- 线程数量受限于操作系统
虚拟线程(Virtual Threads)
- 特点:M:N映射(多个虚拟线程映射到少量平台线程)
- 优势:
- 创建开销极小(约几KB)
- 支持百万级线程
- 自动调度,无需手动管理
虚拟线程示例
// Java 21 虚拟线程创建
public class VirtualThreadExample {public static void main(String[] args) {// 方式1:直接创建Thread virtualThread = Thread.ofVirtual().name("virtual-thread").start(() -> {System.out.println("虚拟线程执行中...");});// 方式2:使用线程池try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {executor.submit(() -> {System.out.println("虚拟线程池任务执行中...");});}}
}
平台线程 vs 虚拟线程对比
特性 | 平台线程 | 虚拟线程 |
---|---|---|
创建开销 | 大(~2MB) | 小(~几KB) |
数量限制 | 数千个 | 数百万个 |
调度方式 | 操作系统 | JVM |
适用场景 | CPU密集型 | I/O密集型 |
阻塞成本 | 高 | 低 |
实践练习
练习1:观察线程状态变化
public class ThreadStateObserver {public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(() -> {try {Thread.sleep(2000);synchronized (ThreadStateObserver.class) {// 模拟等待锁}} catch (InterruptedException e) {Thread.currentThread().interrupt();}});System.out.println("创建后: " + thread.getState()); // NEWthread.start();System.out.println("启动后: " + thread.getState()); // RUNNABLEThread.sleep(100);System.out.println("运行中: " + thread.getState()); // RUNNABLEThread.sleep(3000);System.out.println("结束后: " + thread.getState()); // TERMINATED}
}
练习2:并发vs并行性能测试
public class ConcurrencyVsParallelism {public static void main(String[] args) {int[] array = IntStream.range(0, 1000000).toArray();// 串行执行long start = System.currentTimeMillis();int sum1 = Arrays.stream(array).sum();long serialTime = System.currentTimeMillis() - start;// 并行执行start = System.currentTimeMillis();int sum2 = Arrays.stream(array).parallel().sum();long parallelTime = System.currentTimeMillis() - start;System.out.println("串行结果: " + sum1 + ", 耗时: " + serialTime + "ms");System.out.println("并行结果: " + sum2 + ", 耗时: " + parallelTime + "ms");}
}
总结
本部分介绍了Java并发编程的基础概念:
- 并发与并行:理解多任务执行的不同方式
- 进程与线程:掌握操作系统的基本概念
- 线程状态:了解Java线程的完整生命周期
- 内存模型:理解并发问题的根本原因
- 调度机制:掌握线程调度的原理
- 虚拟线程:了解Java 21的线程模型革新
这些基础知识为后续深入学习并发编程奠定了坚实的基础。下一部分将学习线程的创建与管理。