Java 并发编程:核心原理、实战与避坑
Java 并发编程涉及多线程安全、JVM 内存模型及同步机制。文章解析了线程创建方式、生命周期、synchronized 与 Lock 区别、线程池参数优化及常用工具类 CountDownLatch 等。重点指出 volatile 误用、线程池 OOM、死锁等常见坑点,并提供生产环境配置建议与排查工具,帮助开发者掌握高效安全的并发代码编写方法。

Java 并发编程涉及多线程安全、JVM 内存模型及同步机制。文章解析了线程创建方式、生命周期、synchronized 与 Lock 区别、线程池参数优化及常用工具类 CountDownLatch 等。重点指出 volatile 误用、线程池 OOM、死锁等常见坑点,并提供生产环境配置建议与排查工具,帮助开发者掌握高效安全的并发代码编写方法。

并发编程是 Java 开发的核心技能,也是面试高频考点与生产环境故障高发区。多数开发者在使用多线程时,常面临线程安全(如竞态条件、数据不一致)、死锁、性能瓶颈(如线程上下文切换过多)等问题,且对底层原理(JVM 内存模型、线程调度)理解模糊。本文将从并发编程核心概念切入,深度解析线程创建方式、同步机制、线程池原理、并发工具类等关键知识点,结合 10+ 实战案例拆解常见坑点(如 synchronized 锁升级、volatile 可见性陷阱),提供一套'原理 + 实践 + 优化'的完整方法论,帮助开发者从根源上掌握并发编程,写出高效、安全的多线程代码。
在多核 CPU 时代,并发编程的核心价值在于充分利用硬件资源,提升程序执行效率:
| 概念 | 核心定义 | 举例场景 |
|---|---|---|
| 进程 | 操作系统资源分配的最小单位(拥有独立内存空间、文件句柄等) | 一个 Java 应用程序(JVM 进程) |
| 线程 | 进程内的执行单元(共享进程资源,CPU 调度的最小单位) | Java 程序中的 Thread 实例 |
| 协程 | 用户态轻量级线程(无内核调度开销,由程序自身控制切换) | Spring WebFlux 中的异步任务、Go 语言的 goroutine |
| 并发(Concurrency) | 多个任务在同一时间段内交替执行(CPU 切换快,看似同时) | 单 CPU 核心下多线程处理请求 |
| 并行(Parallelism) | 多个任务在同一时刻同时执行(依赖多核 CPU) | 四核 CPU 同时处理 4 个线程任务 |
| 线程安全 | 多线程并发访问共享资源时,程序行为符合预期(无数据污染、死锁等问题) | 并发环境下 i++ 操作结果正确 |
并发问题的根源在于多线程对共享变量的可见性、原子性、有序性问题,而 JMM 正是为解决这些问题而生:
| 创建方式 | 核心原理 | 优点 | 缺点 | 实战示例 |
|---|---|---|---|---|
| 继承 Thread 类 | 重写 run() 方法,直接调用 start() 启动线程 | 实现简单,代码简洁 | 无法继承其他类(Java 单继承) | class MyThread extends Thread { @Override public void run() {} } |
| 实现 Runnable 接口 | 实现 run() 方法,通过 Thread 实例包装启动 | 可继承其他类,灵活性高 | 无法直接返回结果(需配合 Future) | class MyRunnable implements Runnable { @Override public void run() {} } |
| 实现 Callable 接口 | 实现 call() 方法,通过 FutureTask 包装,支持返回结果和异常抛出 | 支持返回结果、异常处理 | 实现稍复杂,需配合线程池使用 | class MyCallable implements Callable<Integer> { @Override public Integer call() {} } |
Java 线程状态定义在 Thread.State 枚举中,状态流转是面试核心:
start());Object.wait()、LockSupport.park(),需其他线程唤醒);Thread.sleep(1000)、Object.wait(1000));start() 后不能重复启动(会抛出 IllegalThreadStateException);Thread.sleep() 不会释放锁,Object.wait() 会释放锁(需在 synchronized 代码块中调用)。同步机制的核心是保证临界区代码原子执行,避免多线程并发冲突,Java 提供两种核心方案:
this、Class 对象、自定义对象);Mark Word 实现,锁升级过程(无锁→偏向锁→轻量级锁→重量级锁)是性能优化的关键:
public class SynchronizedDemo {
private int count = 0;
// 修饰方法(锁当前实例)
public synchronized void increment() {
count++; // 临界区代码(原子执行)
}
public static void main(String[] args) throws InterruptedException {
SynchronizedDemo demo = new SynchronizedDemo();
// 启动 1000 个线程,每个线程执行 1000 次 increment
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 1000; i++) {
executor.submit(demo::increment);
}
executor.shutdown();
executor.awaitTermination(1, TimeUnit.MINUTES);
System.out.println("最终 count 值:" + demo.count); // 输出 1000000(线程安全)
}
}
java.util.concurrent.locks.Lock 接口,核心实现类 ReentrantLock(可重入锁),相比 synchronized 更灵活:
lock():获取锁(阻塞);tryLock():尝试获取锁(非阻塞,返回 boolean);tryLock(long time, TimeUnit unit):超时获取锁;unlock():释放锁(必须在 finally 中调用,避免死锁);public class LockProducerConsumer {
private final Lock lock = new ReentrantLock();
private final Condition notEmpty = lock.newCondition(); // 非空条件
private final Condition notFull = lock.newCondition(); // 非满条件
private final Queue<Integer> queue = new LinkedList<>();
private static final int CAPACITY = 10; // 队列容量
// 生产者
public void produce(int data) throws InterruptedException {
lock.lock();
try {
// 队列满时等待
while (queue.size() == CAPACITY) {
notFull.await(); // 释放锁,等待 notFull 信号
}
queue.offer(data);
System.out.println("生产者生产:" + data);
notEmpty.signal(); // 唤醒消费者(队列非空)
} finally {
lock.unlock(); // 必须在 finally 释放锁
}
}
// 消费者
public Integer consume() throws InterruptedException {
lock.lock();
try {
// 队列为空时等待
while (queue.isEmpty()) {
notEmpty.await(); // 释放锁,等待 notEmpty 信号
}
Integer data = queue.poll();
System.out.println("消费者消费:" + data);
notFull.signal(); // 唤醒生产者(队列非满)
return data;
} finally {
lock.unlock();
}
}
}
| 对比维度 | synchronized | ReentrantLock |
|---|---|---|
| 锁实现层面 | JVM 层面(隐式锁) | API 层面(显式锁) |
| 锁类型 | 非公平锁(默认),不可配置 | 公平锁/非公平锁(可通过构造函数配置) |
| 锁释放 | 自动释放(代码块执行完毕/异常) | 手动释放(必须在 finally 中调用 unlock()) |
| 功能扩展 | 基础同步功能,无额外 API | 支持中断、超时、条件变量、锁尝试 |
| 性能 | JDK1.6 后优化(锁升级),性能接近 Lock | 高并发场景下性能更优,灵活度高 |
| 适用场景 | 简单同步场景(如单例模式、简单计数器) | 复杂同步场景(如多条件等待、超时重试) |
线程池是并发编程的'性能利器',其核心价值是复用线程、控制线程数量、减少线程创建销毁开销,避免'线程爆炸'(如无限制创建线程导致 OOM)。
Java 线程池的核心实现类是 ThreadPoolExecutor,构造函数参数决定线程池行为:
public ThreadPoolExecutor(int corePoolSize, // 核心线程数(常驻线程,即使空闲也不销毁)
int maximumPoolSize, // 最大线程数(核心线程 + 临时线程的总上限)
long keepAliveTime, // 临时线程空闲存活时间
TimeUnit unit, // keepAliveTime 的时间单位
BlockingQueue<Runnable> workQueue, // 任务阻塞队列(核心线程满时存储任务)
ThreadFactory threadFactory, // 线程创建工厂(自定义线程名称、优先级等)
RejectedExecutionHandler handler) // 任务拒绝策略(队列满 + 最大线程数时的处理方式)
| 拒绝策略类 | 核心逻辑 | 适用场景 |
|---|---|---|
| AbortPolicy(默认) | 直接抛出 RejectedExecutionException 异常 | 不允许任务丢失的场景(如金融交易) |
| CallerRunsPolicy | 由提交任务的线程(如主线程)自己执行任务 | 允许任务延迟执行,不希望抛出异常的场景 |
| DiscardPolicy | 直接丢弃任务,不抛出异常 | 任务可丢失的场景(如日志收集) |
| DiscardOldestPolicy | 丢弃队列中最旧的任务,再尝试提交当前任务 | 任务有优先级,新任务比旧任务重要的场景 |
Executors 提供了 4 种预定义线程池,但生产环境不建议直接使用(存在 OOM 风险):
Executors.newFixedThreadPool(n):固定核心线程数,无临时线程(maximumPoolSize=corePoolSize),队列无界(LinkedBlockingQueue)→ 队列满时 OOM;Executors.newCachedThreadPool():核心线程数 0,临时线程无上限(maximumPoolSize=Integer.MAX_VALUE)→ 线程爆炸 OOM;Executors.newSingleThreadExecutor():单核心线程,队列无界 → 队列满时 OOM;Executors.newScheduledThreadPool(n):定时任务线程池,核心线程数 n,支持延迟/周期性执行任务。线程池参数需根据业务场景(CPU 密集型/IO 密集型)调整:
// 1. 获取 CPU 核心数
int cpuCoreNum = Runtime.getRuntime().availableProcessors();
// 2. 配置 IO 密集型线程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(
cpuCoreNum * 2, // 核心线程数
cpuCoreNum * 2 + 1, // 最大线程数
60L, // 临时线程空闲存活时间
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(1000), // 有界队列(避免 OOM),容量根据业务调整
new ThreadFactory() { // 自定义线程工厂(便于问题排查)
private final AtomicInteger count = new AtomicInteger(0);
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setName("biz-thread-" + count.incrementAndGet());
thread.setPriority(Thread.NORM_PRIORITY);
return thread;
}
},
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略(IO 密集型允许延迟)
);
Java 并发包(java.util.concurrent)提供了多种工具类,简化复杂并发场景的开发:
countDown() 减 1,主线程调用 await() 阻塞,直到计数器为 0。public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(3); // 计数器=3
// 启动 3 个任务线程
for (int i = 1; i <= 3; i++) {
int taskId = i;
new Thread(() -> {
try {
System.out.println("任务" + taskId + "执行中...");
Thread.sleep(1000); // 模拟任务执行
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
latch.countDown(); // 任务执行完毕,计数器减 1
}
}).start();
}
latch.await(); // 主线程阻塞,等待计数器为 0
System.out.println("所有任务执行完毕,主线程继续执行");
}
}
reset())。public class CyclicBarrierDemo {
public static void main(String[] args) {
CyclicBarrier barrier = new CyclicBarrier(3, () -> {
System.out.println("所有线程已到达屏障,开始同步执行...");
}); // 计数器=3,屏障触发时执行回调
for (int i = 1; i <= 3; i++) {
int threadId = i;
new Thread(() -> {
try {
System.out.println("线程" + threadId + "正在前往屏障...");
Thread.sleep(threadId * 1000); // 模拟不同到达时间
barrier.await(); // 到达屏障,阻塞等待其他线程
System.out.println("线程" + threadId + "开始执行任务");
} catch (InterruptedException | BrokenBarrierException e) {
Thread.currentThread().interrupt();
}
}).start();
}
}
}
acquire())后才能访问资源,访问完毕释放许可(release()),许可数为 0 时线程阻塞。public class SemaphoreDemo {
private static final Semaphore semaphore = new Semaphore(2); // 最多 2 个许可
private static final List<String> dbConnections = Arrays.asList("conn1", "conn2", "conn3");
public static void main(String[] args) {
// 启动 5 个线程竞争访问数据库连接
for (int i = 1; i <= 5; i++) {
int threadId = i;
new Thread(() -> {
try {
semaphore.acquire(); // 获取许可(限流)
String conn = dbConnections.get(threadId % 3);
System.out.println("线程" + threadId + "获取连接:" + conn);
Thread.sleep(1000); // 模拟数据库操作
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
semaphore.release(); // 释放许可
System.out.println("线程" + threadId + "释放连接");
}
}).start();
}
}
}
volatile int i=0; i++ 线程安全);AtomicInteger(CAS 实现),复杂逻辑使用 synchronized/Lock。Executors.newFixedThreadPool()(无界队列),高并发下任务堆积导致 OOM;ReentrantLock.tryLock(time, unit) 超时获取锁,避免无限等待;public synchronized void method()),导致所有线程串行执行;Java 并发编程的核心是在保证线程安全的前提下,充分利用硬件资源提升性能。其底层依赖 JMM 解决可见性、原子性、有序性问题,上层通过线程、同步机制、线程池、并发工具类实现复杂并发场景。
学习并发编程的关键在于:
建议开发者在日常开发中,从简单场景(如线程池使用、synchronized 同步)入手,逐步深入复杂场景(如并发工具类、异步编程),同时养成'用工具排查并发问题'的习惯(如 jstack 分析死锁),最终掌握并发编程这一核心技能。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online