开篇概览:并发编程的核心价值
并发(Concurrency) 是指多个任务在同一时间段内交替执行,而并行(Parallelism) 是指多个任务在同一时刻同时执行。Java 通过多线程(Multithreading) 实现并发,使程序能够:
- 提升 CPU 利用率(尤其在多核系统);
- 改善响应性(如 GUI 应用不卡顿);
- 提高吞吐量(如 Web 服务器处理多请求)。
本章将系统讲解 Java 并发编程的核心知识体系:
- 线程与进程基础概念;
- 创建线程的两种方式;
- 线程生命周期与状态;
- 线程同步与锁机制(
synchronized、Lock); - 线程间通信(
wait()/notify()); - 线程池(
ExecutorService); - 高级并发工具简介(
java.util.concurrent包)。
⚠️ 重要前提:
- 线程是轻量级进程,共享进程内存;
- 多线程带来性能提升的同时,也引入线程安全问题(如竞态条件、死锁);
- 并发 ≠ 并行:单核 CPU 也能并发(时间片轮转),多核才能并行。
一、线程与进程的概念
1.1 进程(Process)
- 操作系统分配资源的基本单位;
- 拥有独立的内存空间(堆、栈、代码段等);
- 进程间通信(IPC)成本高(如管道、消息队列)。
1.2 线程(Thread)
- CPU 调度的基本单位;
- 同一进程内的线程共享堆内存,但栈独立;
- 线程间通信成本低(直接读写共享变量);
- 创建/切换开销远小于进程。
1.3 Java 线程模型
- Java 线程是1:1 模型(每个 Java 线程对应一个 OS 线程);
- 由 JVM 和操作系统共同管理;
- 主线程(
main方法所在线程)启动后,可创建子线程。
二、创建线程的两种方式
2.1 方式一:继承 Thread 类
步骤:
- 自定义类继承
Thread; - 重写
run()方法(线程执行体); - 创建对象并调用
start()启动线程。
示例:继承 Thread
// 中文注释:定义一个下载任务线程类
class DownloadThread extends Thread {
private String fileName;
public DownloadThread(String fileName) {
this.fileName = fileName;
}
// 重写 run() 方法:线程启动后执行的逻辑
@Override
public void run() {
System.out.println("开始下载文件: " + fileName);
try {
// 模拟下载耗时操作(休眠2秒)
Thread.sleep(2000);
} catch (InterruptedException e) {
System.err.println("下载被中断: " + e.getMessage());
}
System.out.println("文件下载完成: " + fileName);
}
}
public class ThreadInheritanceDemo {
public static void main(String[] args) {
// 创建两个下载线程
DownloadThread thread1 = new DownloadThread("report.pdf");
DownloadThread thread2 = new DownloadThread("image.jpg");
// 启动线程(调用 start(),而非 run()!)
thread1.start();
thread2.start();
// 主线程继续执行
System.out.println("主线程继续执行其他任务...");
}
}
✅ 关键点:
- 必须调用
start(),直接调用run()是同步执行;- Java 单继承限制:若类已继承其他类,则无法再继承
Thread。
2.2 方式二:实现 Runnable 接口(推荐)
步骤:
- 自定义类实现
Runnable接口; - 实现
run()方法; - 创建
Thread对象,传入Runnable实例; - 调用
start()启动线程。
示例:实现 Runnable
// 中文注释:定义一个任务类,实现 Runnable 接口
class PrintTask implements Runnable {
private String taskName;
public PrintTask(String taskName) {
this.taskName = taskName;
}
@Override
public void run() {
for (int i = 1; i <= 3; i++) {
System.out.println(taskName + " 执行第 " + i + " 次");
try {
Thread.sleep(500); // 模拟任务耗时
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 恢复中断状态
System.err.println(taskName + " 被中断");
}
}
}
}
public class RunnableDemo {
public static void main(String[] args) {
// 创建 Runnable 任务
PrintTask task1 = new PrintTask("任务A");
PrintTask task2 = new PrintTask("任务B");
// 将任务提交给线程执行
Thread thread1 = new Thread(task1);
Thread thread2 = new Thread(task2);
thread1.start();
thread2.start();
System.out.println("主线程结束");
}
}
✅ 优势:
- 避免单继承限制;
- 任务与线程解耦,更符合面向对象设计;
- 可被线程池复用(见后文)。
三、线程的生命周期与状态
Java 线程有 6 种状态(java.lang.Thread.State):
| 状态 | 说明 | 触发条件 |
|---|---|---|
| NEW | 新建 | new Thread() 后,未调用 start() |
| RUNNABLE | 可运行 | 调用 start() 后,等待 CPU 调度(含运行中) |
| BLOCKED | 阻塞 | 等待获取 synchronized 锁 |
| WAITING | 等待 | 调用 wait(), join(), LockSupport.park() |
| TIMED_WAITING | 计时等待 | 调用 sleep(), wait(timeout), join(timeout) |
| TERMINATED | 终止 | run() 执行完毕或异常退出 |
状态转换图
NEW
↓ start()
RUNNABLE ←→ BLOCKED (等待锁)
↓
WAITING / TIMED_WAITING (主动等待)
↓ notify()/超时/中断
RUNNABLE
↓ run()结束
TERMINATED
示例:观察线程状态
public class ThreadStateDemo {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
try {
Thread.sleep(2000); // TIMED_WAITING
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
System.out.println("1. 初始状态: " + thread.getState()); // NEW
thread.start();
System.out.println("2. 启动后状态: " + thread.getState()); // RUNNABLE 或 TIMED_WAITING
Thread.sleep(100); // 确保线程进入 sleep
System.out.println("3. sleep 中状态: " + thread.getState()); // TIMED_WAITING
thread.join(); // 等待线程结束
System.out.println("4. 结束后状态: " + thread.getState()); // TERMINATED
}
}
四、线程同步与锁机制
4.1 问题:竞态条件(Race Condition)
当多个线程并发修改共享数据时,结果依赖于线程执行顺序,导致不可预测的错误。
示例:未同步的计数器
class Counter {
private int count = 0;
public void increment() {
count++; // 非原子操作:读取 → 修改 → 写入
}
public int getCount() {
return count;
}
}
public class RaceConditionDemo {
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
Thread[] threads = new Thread[10];
// 创建10个线程,每个线程递增1000次
for (int i = 0; i < 10; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < 1000; j++) {
counter.increment();
}
});
threads[i].start();
}
// 等待所有线程结束
for (Thread t : threads) {
t.join();
}
System.out.println("期望值: 10000, 实际值: " + counter.getCount());
// 实际值 < 10000(因竞态条件)
}
}
4.2 解决方案一:synchronized 关键字
原理:
- 基于 JVM 内置锁(Monitor Lock);
- 同一时刻只有一个线程能进入同步代码块/方法。
三种用法:
- 同步实例方法:锁当前对象(
this); - 同步静态方法:锁类对象(
Class); - 同步代码块:锁指定对象。
示例:使用 synchronized 修复计数器
class SafeCounter {
private int count = 0;
// 方式1:同步实例方法(锁 this)
public synchronized void increment() {
count++;
}
// 方式2:同步代码块(更灵活)
public void decrement() {
synchronized (this) {
count--;
}
}
public int getCount() {
return count;
}
}
public class SynchronizedDemo {
public static void main(String[] args) throws InterruptedException {
SafeCounter counter = new SafeCounter();
Thread[] threads = new Thread[10];
for (int i = 0; i < 10; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < 1000; j++) {
counter.increment();
}
});
threads[i].start();
}
for (Thread t : threads) {
t.join();
}
System.out.println("期望值: 10000, 实际值: " + counter.getCount()); // 10000
}
}
✅ 关键点:
synchronized是可重入锁(同一线程可多次获取);- 锁对象必须一致(如都用
this)。
4.3 解决方案二:Lock 接口(java.util.concurrent.locks)
优势 vs synchronized:
- 显式加锁/解锁,更灵活;
- 支持尝试加锁(
tryLock()); - 支持可中断锁(
lockInterruptibly()); - 支持公平锁(按请求顺序获取)。
核心接口:Lock
| 方法 | 说明 |
|---|---|
lock() | 获取锁(阻塞) |
unlock() | 释放锁(必须在 finally 中调用) |
tryLock() | 尝试获取锁(立即返回) |
lockInterruptibly() | 可中断地获取锁 |
示例:使用 ReentrantLock
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class LockCounter {
private int count = 0;
private final Lock lock = new ReentrantLock(); // 可重入锁
public void increment() {
lock.lock(); // 获取锁
try {
count++;
} finally {
lock.unlock(); // 必须在 finally 中释放
}
}
public int getCount() {
return count;
}
}
public class LockDemo {
public static void main(String[] args) throws InterruptedException {
LockCounter counter = new LockCounter();
Thread[] threads = new Thread[10];
for (int i = 0; i < 10; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < 1000; j++) {
counter.increment();
}
});
threads[i].start();
}
for (Thread t : threads) {
t.join();
}
System.out.println("期望值: 10000, 实际值: " + counter.getCount()); // 10000
}
}
✅ 最佳实践:
- 永远在
finally块中释放锁;- 优先使用
synchronized(简单场景),复杂场景用Lock。
五、线程间通信:wait() / notify() / notifyAll()
5.1 为什么需要线程通信?
- 线程间需要协调工作(如生产者-消费者);
synchronized仅保证互斥,不提供等待/唤醒机制。
5.2 核心方法(必须在 synchronized 块中调用)
| 方法 | 说明 |
|---|---|
wait() | 释放锁,进入等待队列 |
notify() | 唤醒一个等待线程 |
notifyAll() | 唤醒所有等待线程 |
⚠️ 重要规则:
- 必须在同步代码块中调用;
- 调用对象必须是锁对象;
wait()会释放锁,notify()不会。
5.3 示例:生产者-消费者模型
import java.util.LinkedList;
import java.util.Queue;
// 中文注释:共享缓冲区(线程安全)
class Buffer {
private final Queue<Integer> queue = new LinkedList<>();
private final int capacity = 5;
// 生产者放入数据
public synchronized void produce(int item) throws InterruptedException {
// 缓冲区满时等待
while (queue.size() == capacity) {
System.out.println("缓冲区已满,生产者等待...");
wait(); // 释放锁,进入等待队列
}
queue.offer(item);
System.out.println("生产: " + item + ", 缓冲区大小: " + queue.size());
notifyAll(); // 唤醒所有消费者
}
// 消费者取出数据
public synchronized int consume() throws InterruptedException {
// 缓冲区空时等待
while (queue.isEmpty()) {
System.out.println("缓冲区为空,消费者等待...");
wait(); // 释放锁,进入等待队列
}
int item = queue.poll();
System.out.println("消费: " + item + ", 缓冲区大小: " + queue.size());
notifyAll(); // 唤醒所有生产者
return item;
}
}
public class ProducerConsumerDemo {
public static void main(String[] args) {
Buffer buffer = new Buffer();
// 生产者线程
Thread producer = new Thread(() -> {
for (int i = 1; i <= 10; i++) {
try {
buffer.produce(i);
Thread.sleep(100); // 模拟生产间隔
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
});
// 消费者线程
Thread consumer = new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
buffer.consume();
Thread.sleep(150); // 模拟消费间隔
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
});
producer.start();
consumer.start();
}
}
✅ 关键点:
- 使用
while而非if检查条件(避免虚假唤醒);- 优先使用
notifyAll()(除非确定只需唤醒一个)。
六、线程池(ExecutorService)
6.1 为什么需要线程池?
- 避免频繁创建/销毁线程(开销大);
- 控制并发数量(防止资源耗尽);
- 统一管理线程生命周期。
6.2 核心接口:ExecutorService
| 方法 | 说明 |
|---|---|
submit(Runnable/Callable) | 提交任务,返回 Future |
execute(Runnable) | 提交任务(无返回值) |
shutdown() | 平滑关闭(等待任务完成) |
shutdownNow() | 立即关闭(尝试中断任务) |
6.3 创建线程池:Executors 工厂类
| 方法 | 说明 |
|---|---|
newFixedThreadPool(n) | 固定大小线程池 |
newCachedThreadPool() | 缓存线程池(空闲60秒回收) |
newSingleThreadExecutor() | 单线程池(保证顺序执行) |
newScheduledThreadPool(n) | 支持定时/周期任务 |
⚠️ 生产环境建议:
不要使用Executors!应使用ThreadPoolExecutor手动创建,避免FixedThreadPool的LinkedBlockingQueue无界队列导致 OOM。
6.4 示例:使用线程池处理任务
import java.util.concurrent.*;
public class ThreadPoolDemo {
public static void main(String[] args) {
// 创建固定大小为3的线程池
ExecutorService executor = Executors.newFixedThreadPool(3);
// 提交10个任务
for (int i = 1; i <= 10; i++) {
final int taskId = i;
executor.execute(() -> {
System.out.println("任务 " + taskId + " 由线程 " +
Thread.currentThread().getName() + " 执行");
try {
Thread.sleep(1000); // 模拟任务耗时
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println("任务 " + taskId + " 完成");
});
}
// 关闭线程池(不再接受新任务)
executor.shutdown();
try {
// 等待所有任务完成(最多60秒)
if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
executor.shutdownNow(); // 强制关闭
}
} catch (InterruptedException e) {
executor.shutdownNow();
Thread.currentThread().interrupt();
}
System.out.println("所有任务已完成");
}
}
✅ 输出特点:
- 同一时刻最多3个任务并发执行;
- 线程被复用(如
pool-1-thread-1执行多个任务)。
七、高级并发工具简介(java.util.concurrent)
7.1 Callable 与 Future
Callable:类似Runnable,但有返回值、可抛出异常;Future:获取异步计算结果。
示例:
ExecutorService executor = Executors.newSingleThreadExecutor();
Callable<Integer> task = () -> {
Thread.sleep(1000);
return 42;
};
Future<Integer> future = executor.submit(task);
System.out.println("结果: " + future.get()); // 阻塞直到完成
7.2 CountDownLatch
- 允许一个或多个线程等待其他线程完成操作。
示例:等待所有服务启动
CountDownLatch latch = new CountDownLatch(3);
for (int i = 0; i < 3; i++) {
new Thread(() -> {
// 模拟服务启动
System.out.println("服务启动中...");
latch.countDown(); // 计数减1
}).start();
}
latch.await(); // 等待计数归零
System.out.println("所有服务已启动");
7.3 CyclicBarrier
- 让一组线程互相等待,直到所有线程都到达屏障点。
示例:多线程计算后汇总
CyclicBarrier barrier = new CyclicBarrier(3, () -> {
System.out.println("所有线程计算完成,开始汇总结果");
});
for (int i = 0; i < 3; i++) {
new Thread(() -> {
// 模拟计算
System.out.println("线程 " + Thread.currentThread().getName() + " 计算中");
try {
barrier.await(); // 等待其他线程
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
7.4 并发集合
ConcurrentHashMap:线程安全的 HashMap;CopyOnWriteArrayList:写时复制的 List(读多写少场景);BlockingQueue:阻塞队列(如ArrayBlockingQueue,LinkedBlockingQueue)。
八、总结:并发编程核心原则
| 问题 | 解决方案 |
|---|---|
| 线程创建 | 优先 Runnable + 线程池 |
| 线程安全 | synchronized / Lock |
| 线程通信 | wait()/notify() 或 Condition |
| 任务管理 | ExecutorService |
| 高级协调 | CountDownLatch, CyclicBarrier, Semaphore |
| 安全集合 | ConcurrentHashMap, BlockingQueue |
📌 核心思想:
“并发编程的目标不是让程序跑得更快,而是让程序在多任务环境下正确、高效地运行。”
掌握 Java 并发,是构建高性能、高可靠系统的必备能力。务必牢记:简单场景用synchronized,复杂场景用java.util.concurrent。
4354

被折叠的 条评论
为什么被折叠?



