Java 并发编程核心体系:从 JMM 原理到生产实战
Java 并发编程涵盖 JMM 内存模型、锁机制(synchronized、ReentrantLock、AQS)、JUC 工具类(原子类、容器、线程池、同步器)及生产实战(库存扣减)。文章详解可见性、原子性、有序性原理,对比不同锁实现优劣,提供线程池配置规范与超卖问题解决方案,并总结线上排查方法。

Java 并发编程涵盖 JMM 内存模型、锁机制(synchronized、ReentrantLock、AQS)、JUC 工具类(原子类、容器、线程池、同步器)及生产实战(库存扣减)。文章详解可见性、原子性、有序性原理,对比不同锁实现优劣,提供线程池配置规范与超卖问题解决方案,并总结线上排查方法。

CPU 的运算速度比主存快了上千倍,为了提升性能,CPU 引入了多级缓存、寄存器,编译器和 CPU 会对指令进行重排序优化。这就导致在多线程场景下,线程对变量的修改,其他线程看不到,或者指令执行顺序和预期不一致,引发线程安全问题。
JMM(Java Memory Model,Java 内存模型)是 JSR-133 规范定义的,用来解决多线程场景下的可见性、原子性、有序性问题,屏蔽不同硬件和操作系统的内存访问差异,实现 Java 程序在不同平台的内存访问一致性。
JMM 规定了所有变量都存储在主内存中,每个线程有自己的工作内存,线程对变量的所有操作都必须在工作内存中进行,不能直接读写主内存的变量。线程间的变量传递,必须通过主内存完成。

一个操作要么全部执行成功,要么全部不执行,执行过程中不会被线程调度器中断。
错误示例:多线程 i++ 原子性问题
package com.jam.demo.atomic;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.ObjectUtils;
import java.util.concurrent.CountDownLatch;
@Slf4j
public class AtomicErrorDemo {
private static int count = 0;
private static final int THREAD_COUNT = 10;
private static final int INCREMENT_COUNT = 1000;
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(THREAD_COUNT);
for (int i = 0; i < THREAD_COUNT; i++) {
new Thread(() -> {
try {
for (int j = 0; j < INCREMENT_COUNT; j++) {
count++;
}
} finally {
countDownLatch.countDown();
}
}).start();
}
countDownLatch.await();
// 预期结果 10000,实际结果大概率小于 10000
log.info("最终计数结果:{}", count);
}
}
正确示例:AtomicInteger 解决原子性问题
package com.jam.demo.atomic;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
@Slf4j
public class AtomicCorrectDemo {
private static final AtomicInteger count = new AtomicInteger(0);
private static final int THREAD_COUNT = 10;
private static final int INCREMENT_COUNT = 1000;
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(THREAD_COUNT);
for (int i = 0; i < THREAD_COUNT; i++) {
new Thread(() -> {
try {
for (int j ; j < INCREMENT_COUNT; j++) {
count.incrementAndGet();
}
} {
countDownLatch.countDown();
}
}).start();
}
countDownLatch.await();
log.info(, count.get());
}
}
当一个线程修改了共享变量的值,其他线程能立即感知到这个修改。导致可见性问题的核心原因是:线程修改了工作内存的变量,没有及时刷新到主内存;其他线程没有重新从主内存读取最新的变量值。
错误示例:可见性问题导致死循环
package com.jam.demo.visibility;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class VisibilityErrorDemo {
private static boolean flag = true;
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
while (flag) {
// 空循环,线程不会重新读取主内存的 flag 值
}
log.info("线程感知到 flag 变化,循环结束");
}).start();
Thread.sleep(1000);
flag = false;
log.info("主线程已修改 flag 为 false");
// 程序永远不会结束,子线程无法感知 flag 的变化
}
}
正确示例:volatile 解决可见性问题
package com.jam.demo.visibility;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class VisibilityCorrectDemo {
// volatile 修饰保证可见性
private static volatile boolean flag = true;
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
while (flag) {
// 空循环
}
log.info("线程感知到 flag 变化,循环结束");
}).start();
Thread.sleep(1000);
flag = false;
log.info("主线程已修改 flag 为 false");
// 程序正常结束,子线程成功感知 flag 变化
}
}
volatile 可见性的底层实现:volatile 修饰的变量,写操作后会插入写屏障,强制把工作内存的最新值刷新到主内存;读操作前会插入读屏障,强制从主内存读取最新值,保证每次读取的都是最新的。
程序执行的顺序按照代码的先后顺序执行,编译器和 CPU 不会进行指令重排序。指令重排序是编译器和 CPU 为了提升性能,在不改变单线程程序执行结果的前提下,对指令进行的重排序优化,但在多线程场景下会导致逻辑错误。
最经典的案例是双重检查锁单例模式,未加 volatile 会因指令重排序导致拿到半初始化的对象。对象创建分为三步:1.分配内存空间 2.初始化对象 3.将对象引用指向分配的内存地址。指令重排序可能会把 2 和 3 颠倒,导致线程 A 执行了 1 和 3,还没执行 2,线程 B 此时判断对象不为 null,直接拿到了未初始化的对象,引发空指针异常。
正确示例:volatile+ 双重检查锁实现单例
package com.jam.demo.singleton;
public class SingletonDemo {
// volatile 禁止指令重排序,保证有序性
private static volatile SingletonDemo instance;
private SingletonDemo() {
// 私有构造方法,防止外部实例化
}
/**
* 获取单例实例
* @return 单例对象
*/
public static SingletonDemo getInstance() {
// 第一次检查,避免不必要的加锁
if (instance == null) {
synchronized (SingletonDemo.class) {
// 第二次检查,防止多线程同时进入第一次检查后重复创建
if (instance == null) {
instance = new SingletonDemo();
}
}
}
return instance;
}
}
happens-before 是 JMM 的核心,用来判断多线程场景下是否存在数据竞争、是否线程安全,它不是指 A 操作在 B 操作之前执行,而是说 A 操作的执行结果对 B 操作可见,保证多线程场景下的可见性和有序性。JSR-133 定义了 8 条核心规则:
synchronized 是 Java 原生的互斥锁,保证原子性、可见性、有序性,是可重入锁,底层基于对象头和监视器锁(Monitor)实现。
Java 对象在内存中分为三部分:对象头、实例数据、对齐填充。对象头包含两部分:
Mark Word 根据锁状态分为四种:无锁、偏向锁、轻量级锁、重量级锁。注意:JDK15 及以后,偏向锁默认关闭,需手动开启 -XX:+UseBiasedLocking 参数,因为高并发场景下偏向锁的撤销开销远大于收益。
锁只能升级,不能降级(除偏向锁可撤销到无锁),完整流程如下:

package com.jam.demo.lock;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.CountDownLatch;
@Slf4j
public class SynchronizedDemo {
private static int count = 0;
private static final int THREAD_COUNT = 10;
private static final int INCREMENT_COUNT = 1000;
/**
* 同步方法,锁当前对象实例
*/
public synchronized void increment() {
count++;
}
/**
* 静态同步方法,锁当前类的 Class 对象
*/
public static synchronized void staticIncrement() {
count++;
}
public static void main(String[] args) throws InterruptedException {
SynchronizedDemo demo = new SynchronizedDemo();
CountDownLatch countDownLatch (THREAD_COUNT);
( ; i < THREAD_COUNT; i++) {
(() -> {
{
( ; j < INCREMENT_COUNT; j++) {
(demo) {
count++;
}
}
} {
countDownLatch.countDown();
}
}).start();
}
countDownLatch.await();
log.info(, count);
}
}
Lock 是 JUC 提供的显式锁接口,相比 synchronized,提供了更灵活的锁控制:可中断、可超时、可尝试获取锁、支持公平锁与非公平锁切换。
ReentrantLock 是可重入的独占锁,基于 AQS 实现,默认非公平锁,可通过构造方法传入 true 开启公平锁。注意:unlock() 必须放在 finally 块中,防止异常导致锁无法释放。
package com.jam.demo.lock;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.locks.ReentrantLock;
@Slf4j
public class ReentrantLockDemo {
private static int count = 0;
private static final int THREAD_COUNT = 10;
private static final int INCREMENT_COUNT = 1000;
// 创建非公平锁,传入 true 则为公平锁
private static final ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(THREAD_COUNT);
for (int i = 0; i < THREAD_COUNT; i++) {
(() -> {
{
( ; j < INCREMENT_COUNT; j++) {
lock.lock();
{
count++;
} {
lock.unlock();
}
}
} {
countDownLatch.countDown();
}
}).start();
}
countDownLatch.await();
log.info(, count);
}
}
| 特性 | synchronized | ReentrantLock |
|---|---|---|
| 锁实现 | JVM 原生,基于对象头和监视器锁 | JDK 层面实现,基于 AQS |
| 灵活性 | 低,锁的获取和释放自动完成 | 高,可手动控制锁的获取和释放,支持超时、中断、尝试获取 |
| 公平性 | 仅支持非公平锁 | 支持公平锁和非公平锁 |
| 可重入性 | 支持 | 支持 |
| 条件变量 | 仅支持 1 个(wait/notify) | 支持多个 Condition,可精准唤醒指定线程 |
| 性能 | JDK17 优化后,低竞争场景与 ReentrantLock 持平,高竞争场景略低 | 高竞争场景性能更稳定,可控性更强 |
适用于读多写少的场景,读锁是共享锁,读操作之间不互斥;写锁是排他锁,读和写、写和写之间互斥,大幅提升读多写少场景的并发性能。
核心规则:
package com.jam.demo.lock;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.ObjectUtils;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantReadWriteLock;
@Slf4j
public class ReadWriteLockCacheDemo {
private static final Map<String, Object> CACHE_MAP = new HashMap<>();
private static final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
// 读锁
private static final ReentrantReadWriteLock.ReadLock READ_LOCK = rwLock.readLock();
// 写锁
private static final ReentrantReadWriteLock.WriteLock WRITE_LOCK = rwLock.writeLock();
/**
* 从缓存获取数据
* @param key 缓存 key
* @return 缓存 value
*/
public Object get(String key) {
READ_LOCK.lock();
try {
return CACHE_MAP.get(key);
} finally {
READ_LOCK.unlock();
}
}
/**
* 写入缓存数据
* @param key 缓存 key
* value 缓存 value
*/
{
WRITE_LOCK.lock();
{
CACHE_MAP.put(key, value);
} {
WRITE_LOCK.unlock();
}
}
{
WRITE_LOCK.lock();
{
CACHE_MAP.clear();
} {
WRITE_LOCK.unlock();
}
}
}
JDK8 引入,JDK17 做了性能优化,解决了 ReentrantReadWriteLock 的写锁饥饿问题(大量读线程导致写线程长期无法获取锁),支持三种模式:写锁、悲观读锁、乐观读。
乐观读是核心优势:不需要加锁,返回一个邮戳(stamp),读取完成后用 validate() 方法校验邮戳是否有效,有效则说明读取过程中无写操作,数据安全;无效则升级为悲观读锁,性能远高于传统读写锁。
package com.jam.demo.lock;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.locks.StampedLock;
@Slf4j
public class StampedLockDemo {
private int x;
private int y;
private final StampedLock stampedLock = new StampedLock();
/**
* 写操作,加写锁
* @param x 新的 x 值
* @param y 新的 y 值
*/
public void write(int x, int y) {
// 获取写锁,返回邮戳
long stamp = stampedLock.writeLock();
try {
this.x = x;
this.y = y;
} finally {
// 释放写锁,传入获取锁时的邮戳
stampedLock.unlockWrite(stamp);
}
}
/**
* 读操作,乐观读模式
* @return 计算结果
*/
public int read() {
// 乐观读,返回邮戳
long stamp = stampedLock.tryOptimisticRead();
// 读取数据,无锁
int currentX = this.x;
.y;
(!stampedLock.validate(stamp)) {
stamp = stampedLock.readLock();
{
currentX = .x;
currentY = .y;
} {
stampedLock.unlockRead(stamp);
}
}
currentX + currentY;
}
}
AQS(AbstractQueuedSynchronizer,抽象队列同步器)是 JUC 锁和同步工具类的核心基础,ReentrantLock、CountDownLatch、Semaphore 等所有 JUC 同步工具均基于 AQS 实现。
通俗来讲:AQS 是一个并发编程的基础框架,用一个 volatile 修饰的 int 类型 state 变量表示同步状态,用一个双向链表(CLH 队列)管理等待获取锁的线程,提供了模板方法,子类只需实现 tryAcquire、tryRelease 等核心方法,即可实现自定义同步器。

package com.jam.demo.lock;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
public class CustomAqsLock {
/**
* 自定义同步器,继承 AQS
*/
private static class Sync extends AbstractQueuedSynchronizer {
/**
* 尝试获取锁
* @param arg 获取参数
* @return 是否获取成功
*/
@Override
protected boolean tryAcquire(int arg) {
// CAS 设置 state,state=0 表示无锁,设置为 1 表示获取锁成功
if (compareAndSetState(0, 1)) {
// 设置当前线程为锁的持有者
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
/**
* 尝试释放锁
* @param arg 释放参数
* @return 是否释放成功
*/
@Override
protected boolean tryRelease(int arg) {
// 校验当前线程是否是锁的持有者
if (getExclusiveOwnerThread() != Thread.currentThread()) {
throw new IllegalMonitorStateException("当前线程不是锁的持有者");
}
// 设置 state 为 0,释放锁
setState();
setExclusiveOwnerThread();
;
}
{
getState() == ;
}
}
();
{
sync.acquire();
}
{
sync.tryAcquire();
}
{
sync.release();
}
{
sync.isHeldExclusively();
}
}
原子类位于 java.util.concurrent.atomic 包下,底层基于 CAS+volatile 实现,无锁化,高并发场景下性能远高于锁。JDK17 中原子类分为四大类:
CAS(Compare And Swap,比较并交换)是 CPU 级别的原子指令,需要三个参数:内存地址 V、预期值 A、新值 B,只有当 V 的值等于 A 时,才会把 V 的值更新为 B,否则不做任何操作,返回当前值。
CAS 三大核心问题与解决方案:
AtomicStampedReference 解决 ABA 问题示例
package com.jam.demo.atomic;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.atomic.AtomicStampedReference;
@Slf4j
public class AtomicStampedReferenceDemo {
// 初始化引用和版本号
private static final AtomicStampedReference<Integer> reference = new AtomicStampedReference<>(100, 1);
public static void main(String[] args) throws InterruptedException {
// 线程 1:执行 ABA 操作
Thread thread1 = new Thread(() -> {
int stamp = reference.getStamp();
log.info("线程 1 初始版本号:{},初始值:{}", stamp, reference.getReference());
// 100 -> 200
reference.compareAndSet(100, 200, stamp, stamp + 1);
// 200 -> 100
reference.compareAndSet(200, 100, reference.getStamp(), reference.getStamp() + 1);
log.info("线程 1 完成 ABA 操作,最终版本号:{},最终值:{}", reference.getStamp(), reference.getReference());
});
// 线程 2:尝试更新值
Thread thread2 = new Thread(() -> {
int reference.getStamp();
log.info(, stamp, reference.getReference());
{
Thread.sleep();
} (InterruptedException e) {
Thread.currentThread().interrupt();
log.error(, e);
}
reference.compareAndSet(, , stamp, stamp + );
log.info(, result, reference.getStamp());
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
}
}
VarHandle 是 JDK9 引入的特性,JDK17 做了优化,替代了 Unsafe 类,提供了更安全、更灵活的内存操作,支持各种类型的 CAS、内存屏障操作,性能更高,是 JDK9+ 推荐的原子操作方式。
package com.jam.demo.atomic;
import lombok.extern.slf4j.Slf4j;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
@Slf4j
public class VarHandleDemo {
private volatile int count = 0;
// 定义 VarHandle
private static final VarHandle COUNT_HANDLE;
static {
try {
// 初始化 VarHandle,绑定 count 字段
COUNT_HANDLE = MethodHandles.lookup().findVarHandle(VarHandleDemo.class, "count", int.class);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new ExceptionInInitializerError(e);
}
}
/**
* 原子递增
* @return 递增后的值
*/
public int incrementAndGet() {
int prev;
int next;
do {
// 获取当前值
prev = (int) COUNT_HANDLE.getVolatile(this);
next = prev + 1;
// CAS 更新,失败则自旋重试
} while (!COUNT_HANDLE.compareAndSet(this, prev, next));
return next;
}
public static void InterruptedException {
();
;
;
Thread[] threads = [threadCount];
( ; i < threadCount; i++) {
threads[i] = (() -> {
( ; j < incrementCount; j++) {
demo.incrementAndGet();
}
});
threads[i].start();
}
(Thread thread : threads) {
thread.join();
}
log.info(, demo.count);
}
}
日常开发中,很多开发者使用 Collections.synchronizedList() 等同步容器,但性能极低,因为所有方法都用 synchronized 修饰,同一时间只能有一个线程访问。JUC 提供了更高效的并发容器,针对高并发场景做了深度优化。
高并发场景下的线程安全 HashMap,替代 HashMap 和 Hashtable。JDK17 的 ConcurrentHashMap 底层结构为数组 + 链表 + 红黑树,核心优化点:
核心容器对比
| 容器 | 线程安全 | 锁机制 | 性能 | 适用场景 |
|---|---|---|---|---|
| HashMap | 否 | 无锁 | 最高 | 单线程场景 |
| Hashtable | 是 | 全表 synchronized 锁 | 极低 | 已废弃,不推荐使用 |
| Collections.synchronizedMap | 是 | 全表 synchronized 锁 | 低 | 低并发场景 |
| ConcurrentHashMap | 是 | 桶级细粒度锁+CAS | 高 | 高并发读写场景 |
ConcurrentHashMap 缓存实现示例
package com.jam.demo.container;
import com.alibaba.fastjson2.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;
import java.util.concurrent.ConcurrentHashMap;
@Slf4j
public class ConcurrentHashMapCacheDemo {
private static final ConcurrentHashMap<String, Object> LOCAL_CACHE = new ConcurrentHashMap<>();
/**
* 存入缓存
* @param key 缓存 key
* @param value 缓存 value
*/
public void put(String key, Object value) {
if (!StringUtils.hasText(key) || ObjectUtils.isEmpty(value)) {
log.warn("缓存 key 或 value 为空");
return;
}
LOCAL_CACHE.put(key, value);
}
/**
* 获取缓存
* @param key 缓存 key
* @return 缓存 value
*/
public Object get(String key) {
if (!StringUtils.hasText(key)) {
return null;
}
return LOCAL_CACHE.get(key);
}
/**
* 不存在则存入,原子操作
* @param key 缓存 key
* @param value 缓存 value
* @return 已存在的 value 或新存入的 value
*/
public Object putIfAbsent(String key, Object value) {
if (!StringUtils.hasText(key) || ObjectUtils.isEmpty(value)) {
return ;
}
LOCAL_CACHE.putIfAbsent(key, value);
}
{
(!StringUtils.hasText(key)) {
;
}
LOCAL_CACHE.remove(key);
}
}
高并发读多写少场景下的线程安全 List,替代 ArrayList 和 Vector。核心原理是写时复制:当进行 add、set、remove 等写操作时,会复制一份新的数组,在新数组上完成修改后,将数组引用指向新数组;读操作无需加锁,直接读取当前数组。
优缺点与适用场景
package com.jam.demo.container;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.CollectionUtils;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
@Slf4j
public class CopyOnWriteArrayListDemo {
// 初始化 CopyOnWriteArrayList
private static final CopyOnWriteArrayList<String> WHITE_LIST = new CopyOnWriteArrayList<>();
/**
* 批量添加白名单
* @param list 白名单列表
*/
public void addWhiteList(List<String> list) {
if (CollectionUtils.isEmpty(list)) {
return;
}
WHITE_LIST.addAll(list);
}
/**
* 判断是否在白名单中
* @param value 待校验的值
* @return 是否在白名单中
*/
public boolean isInWhiteList(String value) {
if (!StringUtils.hasText(value)) {
return false;
}
return WHITE_LIST.contains(value);
}
public static void main(String[] args) {
CopyOnWriteArrayListDemo demo = new CopyOnWriteArrayListDemo();
// 初始化白名单
demo.addWhiteList(Lists.newArrayList(, ));
log.info(, demo.isInWhiteList());
log.info(, demo.isInWhiteList());
}
}
线程池是生产环境中使用最频繁的并发工具,也是最容易踩坑的。阿里巴巴 Java 开发手册明确禁止使用 Executors 创建线程池,必须通过 ThreadPoolExecutor 构造方法手动创建。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)

package com.jam.demo.threadpool;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
@Slf4j
@Configuration
public class ThreadPoolConfig {
/**
* CPU 核心数
*/
private static final int CPU_CORE = Runtime.getRuntime().availableProcessors();
/**
* 核心线程数
*/
private static final int CORE_POOL_SIZE = CPU_CORE * 2;
/**
* 最大线程数
*/
private static final int MAX_POOL_SIZE = CPU_CORE * 4;
/**
* 空闲存活时间
*/
private static final long KEEP_ALIVE_TIME = 60L;
/**
* 工作队列容量
*/
private static ;
;
ThreadPoolExecutor {
()
.setNameFormat(THREAD_NAME_PREFIX + )
.setDaemon()
.build();
(
CORE_POOL_SIZE,
MAX_POOL_SIZE,
KEEP_ALIVE_TIME,
TimeUnit.SECONDS,
<>(QUEUE_CAPACITY),
threadFactory,
.AbortPolicy()
);
log.info(,
CORE_POOL_SIZE, MAX_POOL_SIZE, QUEUE_CAPACITY);
threadPool;
}
}
虚拟线程是 JDK19 引入的预览特性,JDK21 正式发布,JDK17 可通过--enable-preview 参数开启。虚拟线程是 JVM 管理的轻量级线程,不依赖操作系统内核线程,创建和销毁开销极低,可支持百万级并发,特别适合 IO 密集型任务(网络请求、数据库操作等)。
package com.jam.demo.threadpool;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.Executors;
@Slf4j
public class VirtualThreadDemo {
public static void main(String[] args) {
// 创建虚拟线程执行器
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
// 提交 10000 个任务,虚拟线程可轻松支持
for (int i = 0; i < 10000; i++) {
int taskNum = i;
executor.submit(() -> {
log.info("虚拟线程执行任务:{}", taskNum);
try {
// 模拟 IO 操作
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.error("任务执行中断", e);
}
});
}
}
log.info("所有任务执行完成");
}
}
JUC 提供了丰富的同步工具类,用于多线程之间的协作,高频使用的有 CountDownLatch、CyclicBarrier、Semaphore。
一次性同步工具,允许一个或多个线程等待其他线程完成操作后再执行,基于 AQS 实现,用 state 变量表示计数,countDown() 方法将 state 减 1,await() 方法阻塞直到 state 变为 0。
适用场景:并行任务汇总,比如多个线程并行查询数据,主线程等待所有线程查询完成后汇总结果。
package com.jam.demo.sync;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.CountDownLatch;
@Slf4j
public class CountDownLatchDemo {
private static final int TASK_COUNT = 5;
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(TASK_COUNT);
for (int i = 0; i < TASK_COUNT; i++) {
int taskNum = i;
new Thread(() -> {
try {
log.info("任务{}开始执行", taskNum);
// 模拟任务执行
Thread.sleep(1000);
log.info("任务{}执行完成", taskNum);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.error("任务执行中断", e);
} finally {
// 计数减 1
countDownLatch.countDown();
}
}).start();
}
// 主线程阻塞,等待所有任务完成
log.info("主线程等待所有任务执行完成");
countDownLatch.await();
log.info();
}
}
可重复使用的同步工具,允许一组线程互相等待,直到所有线程都到达栅栏位置,然后一起执行后续操作。基于 ReentrantLock 和 Condition 实现,计数变为 0 后会自动重置,可重复使用。
适用场景:多线程并行计算,每个阶段都需要等待所有线程完成后再进入下一个阶段,比如压力测试,多个线程同时启动执行压测。
CountDownLatch 与 CyclicBarrier 核心区别
| 特性 | CountDownLatch | CyclicBarrier |
|---|---|---|
| 可重用性 | 一次性,计数变为 0 后无法重置 | 可重复使用,计数变为 0 后自动重置 |
| 等待对象 | 主线程等待多个工作线程完成 | 多个工作线程互相等待,全部到达后一起执行 |
| 计数方式 | 线程调用 countDown() 减 1,不阻塞 | 线程调用 await() 减 1,阻塞等待 |
| 高级特性 | 无 | 支持传入 Runnable 任务,所有线程到达后优先执行 |
用于控制同时访问特定资源的线程数量,实现流量控制,基于 AQS 实现,用 state 变量表示可用的许可数量,acquire() 方法获取许可,release() 方法释放许可。
适用场景:流量控制,比如数据库连接池、接口限流、秒杀场景的并发控制。
package com.jam.demo.sync;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.Semaphore;
@Slf4j
public class SemaphoreLimitDemo {
// 最大并发数 10
private static final int MAX_CONCURRENT = 10;
private static final Semaphore semaphore = new Semaphore(MAX_CONCURRENT);
/**
* 模拟接口请求
* @param requestId 请求 ID
*/
public void handleRequest(String requestId) {
try {
// 获取许可,获取不到则阻塞
semaphore.acquire();
log.info("请求{}获取许可,开始处理", requestId);
// 模拟接口处理
Thread.sleep(500);
log.info("请求{}处理完成,释放许可", requestId);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.error("请求处理中断", e);
} finally {
// 释放许可
semaphore.release();
}
}
public static void main(String[] args) {
SemaphoreLimitDemo demo = new ();
( ; i < ; i++) {
+ i;
(() -> demo.handleRequest(requestId)).start();
}
}
}
电商秒杀场景中,用户下单扣减商品库存,高并发场景下极易出现超卖问题(库存扣减为负数)。本文基于 JDK17、SpringBoot3、MyBatis-Plus、MySQL8.0,通过多种方案解决超卖问题,所有代码均可直接编译运行。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.5</version>
<relativePath/>
</parent>
<groupId>com.jam</groupId>
<artifactId>concurrency-stock-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>concurrency-stock-demo</name>
<description>并发库存扣减实战 demo</description>
<properties>
<>17
3.5.7
1.18.32
2.0.52
33.2.0-jre
2.5.0
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-jdbc
com.baomidou
mybatis-plus-boot-starter
${mybatis-plus.version}
com.mysql
mysql-connector-j
runtime
org.projectlombok
lombok
${lombok.version}
provided
com.alibaba.fastjson2
fastjson2
${fastjson2.version}
com.google.guava
guava
${guava.version}
org.springdoc
springdoc-openapi-starter-webmvc-ui
${springdoc.version}
org.springframework.boot
spring-boot-starter-test
test
org.springframework.boot
spring-boot-maven-plugin
org.projectlombok
lombok
CREATE TABLE `t_product_stock` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键 ID',
`product_id` bigint NOT NULL COMMENT '商品 ID',
`stock_num` int NOT NULL DEFAULT '0' COMMENT '库存数量',
`version` int NOT NULL DEFAULT '0' COMMENT '乐观锁版本号',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_product_id` (`product_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='商品库存表';
package com.jam.demo.entity;
import com.baomidou.mybatisplus.annotation.*;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@TableName("t_product_stock")
@Schema(description = "商品库存实体")
public class ProductStock {
@Schema(description = "主键 ID")
@TableId(type = IdType.AUTO)
private Long id;
@Schema(description = "商品 ID")
private Long productId;
@Schema(description = "库存数量")
private Integer stockNum;
@Schema(description = "乐观锁版本号")
@Version
private Integer version;
@Schema(description = "创建时间")
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@Schema(description = "更新时间")
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}
package com.jam.demo.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jam.demo.entity.ProductStock;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Update;
public interface ProductStockMapper extends BaseMapper<ProductStock> {
/**
* 悲观锁扣减库存
* @param productId 商品 ID
* @param num 扣减数量
* @return 影响行数
*/
@Update("UPDATE t_product_stock SET stock_num = stock_num - #{num} WHERE product_id = #{productId} AND stock_num >= #{num}")
int deductStock(@Param("productId") Long productId, @Param("num") Integer num);
}
package com.jam.demo.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.jam.demo.entity.ProductStock;
import com.jam.demo.mapper.ProductStockMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Slf4j
@Service
@RequiredArgsConstructor
public class StockErrorService {
private final ProductStockMapper productStockMapper;
/**
* 错误扣减实现,无锁控制,高并发下超卖
* @param productId 商品 ID
* @param num 扣减数量
* @return 扣减结果
*/
@Transactional(rollbackFor = Exception.class)
public boolean deductStock(Long productId, Integer num) {
// 1.查询库存
LambdaQueryWrapper<ProductStock> queryWrapper = new LambdaQueryWrapper<ProductStock>()
.eq(ProductStock::getProductId, productId);
ProductStock productStock = productStockMapper.selectOne(queryWrapper);
if (ObjectUtils.isEmpty(productStock)) {
log.error("商品库存不存在,productId:{}", productId);
return false;
}
// 2.校验库存
if (productStock.getStockNum() < num) {
log.error("商品库存不足,productId:{},当前库存:{},扣减数量:{}", productId, productStock.getStockNum(), num);
return ;
}
productStock.setStockNum(productStock.getStockNum() - num);
productStockMapper.updateById(productStock);
log.info(, productId, productStock.getStockNum());
;
}
}
问题分析:高并发场景下,多个线程同时查询到库存充足,同时执行扣减,导致库存扣减为负数,出现超卖。
基于 MySQL 行锁实现,通过 for update 锁定行记录,保证同一时间只有一个线程能操作库存,避免超卖,使用编程式事务控制事务边界。
package com.jam.demo.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.jam.demo.entity.ProductStock;
import com.jam.demo.mapper.ProductStockMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
@Slf4j
@Service
@RequiredArgsConstructor
public class StockPessimisticLockService {
private final ProductStockMapper productStockMapper;
private final PlatformTransactionManager transactionManager;
/**
* 悲观锁扣减库存,编程式事务控制
* @param productId 商品 ID
* @param num 扣减数量
* @return 扣减结果
*/
public boolean deductStock(Long productId, Integer num) {
// 编程式事务定义
DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
definition.setPropagationBehavior(DefaultTransactionDefinition.PROPAGATION_REQUIRED);
TransactionStatus status = transactionManager.getTransaction(definition);
try {
// 1.查询库存并加悲观锁,for update 锁定行记录
LambdaQueryWrapper<ProductStock> queryWrapper = new <ProductStock>()
.eq(ProductStock::getProductId, productId)
.last();
productStockMapper.selectOne(queryWrapper);
(ObjectUtils.isEmpty(productStock)) {
log.error(, productId);
transactionManager.rollback(status);
;
}
(productStock.getStockNum() < num) {
log.error(, productId, productStock.getStockNum(), num);
transactionManager.rollback(status);
;
}
productStock.setStockNum(productStock.getStockNum() - num);
productStockMapper.updateById(productStock);
transactionManager.commit(status);
log.info(, productId, productStock.getStockNum());
;
} (Exception e) {
transactionManager.rollback(status);
log.error(, productId, e);
;
}
}
}
基于版本号机制实现,MyBatis-Plus 内置乐观锁插件,更新时校验版本号,版本号匹配才更新成功,否则更新失败,避免超卖,性能高于悲观锁,适合读多写少的场景。
package com.jam.demo.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MybatisPlusConfig {
/**
* 配置 MyBatis-Plus 拦截器
* @return MybatisPlusInterceptor
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 乐观锁插件
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
// 分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
package com.jam.demo.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.jam.demo.entity.ProductStock;
import com.jam.demo.mapper.ProductStockMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
@Slf4j
@Service
@RequiredArgsConstructor
public class StockOptimisticLockService {
private final ProductStockMapper productStockMapper;
private final PlatformTransactionManager transactionManager;
/**
* 最大重试次数
*/
private static final int MAX_RETRY = 3;
/**
* 乐观锁扣减库存,带重试机制
* @param productId 商品 ID
* @param num 扣减数量
* @return 扣减结果
*/
public boolean deductStock(Long productId, Integer num) {
int retryCount = 0;
// 自旋重试
while (retryCount < MAX_RETRY) {
// 编程式事务定义
DefaultTransactionDefinition ();
definition.setPropagationBehavior(DefaultTransactionDefinition.PROPAGATION_REQUIRED);
transactionManager.getTransaction(definition);
{
LambdaQueryWrapper<ProductStock> queryWrapper = <ProductStock>()
.eq(ProductStock::getProductId, productId);
productStockMapper.selectOne(queryWrapper);
(ObjectUtils.isEmpty(productStock)) {
log.error(, productId);
transactionManager.rollback(status);
;
}
(productStock.getStockNum() < num) {
log.error(, productId, productStock.getStockNum(), num);
transactionManager.rollback(status);
;
}
productStock.setStockNum(productStock.getStockNum() - num);
productStockMapper.updateById(productStock);
(updateCount > ) {
transactionManager.commit(status);
log.info(,
productId, productStock.getStockNum(), retryCount);
;
}
transactionManager.rollback(status);
retryCount++;
Thread.sleep();
} (Exception e) {
transactionManager.rollback(status);
log.error(, productId, e);
;
}
}
log.error(, productId);
;
}
}
直接通过 SQL 语句完成库存校验和扣减,利用 MySQL 的 InnoDB 引擎事务特性,保证操作的原子性,性能最高,实现最简单。
package com.jam.demo.service;
import com.jam.demo.mapper.ProductStockMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
@Slf4j
@Service
@RequiredArgsConstructor
public class StockSqlAtomicService {
private final ProductStockMapper productStockMapper;
private final PlatformTransactionManager transactionManager;
/**
* SQL 原子扣减库存,数据库层面保证原子性
* @param productId 商品 ID
* @param num 扣减数量
* @return 扣减结果
*/
public boolean deductStock(Long productId, Integer num) {
DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
definition.setPropagationBehavior(DefaultTransactionDefinition.PROPAGATION_REQUIRED);
TransactionStatus status = transactionManager.getTransaction(definition);
try {
// 直接通过 SQL 完成校验和扣减,原子操作
int updateCount = productStockMapper.deductStock(productId, num);
if (updateCount > 0) {
transactionManager.commit(status);
log.info(, productId);
;
}
transactionManager.rollback(status);
log.error(, productId);
;
} (Exception e) {
transactionManager.rollback(status);
log.error(, productId, e);
;
}
}
}
死锁是指两个或多个线程互相等待对方持有的锁,导致永久阻塞的现象。死锁的四个必要条件,缺一不可,破坏其中一个即可避免死锁:
避坑方法:
死锁示例与修复
package com.jam.demo.deadlock;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class DeadLockDemo {
private static final Object LOCK_A = new Object();
private static final Object LOCK_B = new Object();
/**
* 死锁示例:线程 1 持有 LOCK_A,等待 LOCK_B;线程 2 持有 LOCK_B,等待 LOCK_A
*/
public static void deadLockDemo() {
new Thread(() -> {
synchronized (LOCK_A) {
log.info("线程 1 获取到 LOCK_A");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.error("线程中断", e);
}
log.info("线程 1 等待获取 LOCK_B");
synchronized (LOCK_B) {
log.info("线程 1 获取到 LOCK_B");
}
}
}, "dead-lock-thread-1").start();
new Thread(() -> {
synchronized (LOCK_B) {
log.info("线程 2 获取到 LOCK_B");
try {
Thread.sleep();
} (InterruptedException e) {
Thread.currentThread().interrupt();
log.error(, e);
}
log.info();
(LOCK_A) {
log.info();
}
}
}, ).start();
}
{
(() -> {
(LOCK_A) {
log.info();
{
Thread.sleep();
} (InterruptedException e) {
Thread.currentThread().interrupt();
log.error(, e);
}
log.info();
(LOCK_B) {
log.info();
}
}
}, ).start();
(() -> {
(LOCK_A) {
log.info();
{
Thread.sleep();
} (InterruptedException e) {
Thread.currentThread().interrupt();
log.error(, e);
}
log.info();
(LOCK_B) {
log.info();
}
}
}, ).start();
}
{
deadLockDemo();
}
}
jps -l 获取 Java 进程的 PIDjstack <PID> 查看线程堆栈信息,jstack 会自动检测死锁,输出死锁的线程、持有的锁、等待的锁Java 并发编程的核心,不是背会 API 和面试题,而是理解底层的 JMM 内存模型、锁的实现原理、AQS 的核心逻辑。只有搞懂了底层原理,才能在实际项目中正确使用并发工具,避免踩坑,快速定位和解决线上问题。

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