跳到主要内容
Java 并发编程核心体系:从 JMM 原理到生产实战 | 极客日志
Java java 算法
Java 并发编程核心体系:从 JMM 原理到生产实战 Java 并发编程涵盖 JMM 内存模型、锁机制(synchronized、ReentrantLock、AQS)、JUC 工具类(原子类、容器、线程池、同步器)及生产实战(库存扣减)。文章详解可见性、原子性、有序性原理,对比不同锁实现优劣,提供线程池配置规范与超卖问题解决方案,并总结线上排查方法。
蜜桃汽水 发布于 2026/3/26 更新于 2026/7/21 45 浏览一、并发编程的核心基石:JMM 内存模型与三大特性
1.1 为什么需要 JMM 内存模型
CPU 的运算速度比主存快了上千倍,为了提升性能,CPU 引入了多级缓存、寄存器,编译器和 CPU 会对指令进行重排序优化。这就导致在多线程场景下,线程对变量的修改,其他线程看不到,或者指令执行顺序和预期不一致,引发线程安全问题。
JMM(Java Memory Model,Java 内存模型)是 JSR-133 规范定义的,用来解决多线程场景下的可见性、原子性、有序性问题,屏蔽不同硬件和操作系统的内存访问差异,实现 Java 程序在不同平台的内存访问一致性。
1.2 JMM 核心结构
JMM 规定了所有变量都存储在主内存中,每个线程有自己的工作内存,线程对变量的所有操作都必须在工作内存中进行,不能直接读写主内存的变量。线程间的变量传递,必须通过主内存完成。
1.3 并发编程三大核心特性
1.3.1 原子性
一个操作要么全部执行成功,要么全部不执行,执行过程中不会被线程调度器中断。
基本数据类型的赋值操作(JDK17 64 位 JVM 已保证 long、double 的原子性)是原子操作
复合操作(如 i++,分为读取 - 修改 - 写入三步)不具备原子性
错误示例:多线程 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 ;
InterruptedException {
(THREAD_COUNT);
( ; i < THREAD_COUNT; i++) {
(() -> {
{
( ; j < INCREMENT_COUNT; j++) {
count++;
}
} {
countDownLatch.countDown();
}
}).start();
}
countDownLatch.await();
log.info( , count);
}
}
public
static
void
main
(String[] args)
throws
CountDownLatch
countDownLatch
=
new
CountDownLatch
for
int
i
=
0
new
Thread
try
for
int
j
=
0
finally
"最终计数结果:{}"
正确示例: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 = 0 ; j < INCREMENT_COUNT; j++) {
count.incrementAndGet();
}
} finally {
countDownLatch.countDown();
}
}).start();
}
countDownLatch.await();
log.info("最终计数结果:{}" , count.get());
}
}
1.3.2 可见性 当一个线程修改了共享变量的值,其他线程能立即感知到这个修改。导致可见性问题的核心原因是:线程修改了工作内存的变量,没有及时刷新到主内存;其他线程没有重新从主内存读取最新的变量值。
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) {
}
log.info("线程感知到 flag 变化,循环结束" );
}).start();
Thread.sleep(1000 );
flag = false ;
log.info("主线程已修改 flag 为 false" );
}
}
package com.jam.demo.visibility;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class VisibilityCorrectDemo {
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" );
}
}
volatile 可见性的底层实现:volatile 修饰的变量,写操作后会插入写屏障,强制把工作内存的最新值刷新到主内存;读操作前会插入读屏障,强制从主内存读取最新值,保证每次读取的都是最新的。
1.3.3 有序性 程序执行的顺序按照代码的先后顺序执行,编译器和 CPU 不会进行指令重排序。指令重排序是编译器和 CPU 为了提升性能,在不改变单线程程序执行结果的前提下,对指令进行的重排序优化,但在多线程场景下会导致逻辑错误。
最经典的案例是双重检查锁单例模式,未加 volatile 会因指令重排序导致拿到半初始化的对象。对象创建分为三步:1.分配内存空间 2.初始化对象 3.将对象引用指向分配的内存地址。指令重排序可能会把 2 和 3 颠倒,导致线程 A 执行了 1 和 3,还没执行 2,线程 B 此时判断对象不为 null,直接拿到了未初始化的对象,引发空指针异常。
package com.jam.demo.singleton;
public class SingletonDemo {
private static volatile SingletonDemo instance;
private SingletonDemo () {
}
public static SingletonDemo getInstance () {
if (instance == null ) {
synchronized (SingletonDemo.class) {
if (instance == null ) {
instance = new SingletonDemo ();
}
}
}
return instance;
}
}
1.4 happens-before 核心规则 happens-before 是 JMM 的核心,用来判断多线程场景下是否存在数据竞争、是否线程安全,它不是指 A 操作在 B 操作之前执行,而是说 A 操作的执行结果对 B 操作可见,保证多线程场景下的可见性和有序性。JSR-133 定义了 8 条核心规则:
程序次序规则:一个线程内,按照代码顺序,前面的操作 happens-before 于后面的操作
锁规则:一个 unlock 操作 happens-before 于后续对同一个锁的 lock 操作
volatile 变量规则:对一个 volatile 变量的写操作 happens-before 于后续对这个变量的读操作
线程启动规则:Thread 的 start() 方法 happens-before 于该线程内的所有操作
线程终止规则:线程内的所有操作 happens-before 于其他线程检测到该线程终止
线程中断规则:对线程 interrupt() 方法的调用 happens-before 于被中断线程的代码检测到中断事件的发生
对象终结规则:一个对象的初始化完成 happens-before 于它的 finalize() 方法的开始
传递性规则:如果 A happens-before B,B happens-before C,那么 A happens-before C
二、并发编程的核心锁机制:从底层原理到实战选型
2.1 synchronized 底层原理与 JDK17 优化 synchronized 是 Java 原生的互斥锁,保证原子性、可见性、有序性,是可重入锁,底层基于对象头和监视器锁(Monitor)实现。
2.1.1 对象头与锁状态 Java 对象在内存中分为三部分:对象头、实例数据、对齐填充。对象头包含两部分:
Mark Word:标记字段,64 位 JVM 中占 8 字节,存储对象的 hashCode、分代年龄、锁状态等信息
Class Pointer:类型指针,指向对象的类元数据
Mark Word 根据锁状态分为四种:无锁、偏向锁、轻量级锁、重量级锁。注意:JDK15 及以后,偏向锁默认关闭,需手动开启 -XX:+UseBiasedLocking 参数,因为高并发场景下偏向锁的撤销开销远大于收益 。
2.1.2 锁升级流程 锁只能升级,不能降级(除偏向锁可撤销到无锁),完整流程如下:
偏向锁:单线程访问时,将线程 ID 记录在 Mark Word 中,后续访问无需 CAS 操作,几乎无开销,适合单线程频繁加锁场景
轻量级锁:多线程交替访问时,线程在栈帧中创建锁记录,用 CAS 将 Mark Word 替换为指向锁记录的指针,成功则获取锁,失败则自旋等待,不会阻塞线程,适合锁持有时间短的场景
重量级锁:多线程同时竞争,自适应自旋超过阈值后升级为重量级锁,底层依赖操作系统互斥量(Mutex)实现,线程会被阻塞,上下文切换开销大,适合锁持有时间长的场景
2.1.3 synchronized 正确使用示例 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++;
}
public static synchronized void staticIncrement () {
count++;
}
public static void main (String[] args) throws InterruptedException {
SynchronizedDemo demo = new SynchronizedDemo ();
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++) {
synchronized (demo) {
count++;
}
}
} finally {
countDownLatch.countDown();
}
}).start();
}
countDownLatch.await();
log.info("最终计数结果:{}" , count);
}
}
2.2 Lock 体系核心实现与实战选型 Lock 是 JUC 提供的显式锁接口,相比 synchronized,提供了更灵活的锁控制:可中断、可超时、可尝试获取锁、支持公平锁与非公平锁切换。
2.2.1 ReentrantLock 核心使用 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 ;
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++) {
new Thread (() -> {
try {
for (int j = 0 ; j < INCREMENT_COUNT; j++) {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
} finally {
countDownLatch.countDown();
}
}).start();
}
countDownLatch.await();
log.info("最终计数结果:{}" , count);
}
}
2.2.2 synchronized 与 ReentrantLock 核心区别 特性 synchronized ReentrantLock 锁实现 JVM 原生,基于对象头和监视器锁 JDK 层面实现,基于 AQS 灵活性 低,锁的获取和释放自动完成 高,可手动控制锁的获取和释放,支持超时、中断、尝试获取 公平性 仅支持非公平锁 支持公平锁和非公平锁 可重入性 支持 支持 条件变量 仅支持 1 个(wait/notify) 支持多个 Condition,可精准唤醒指定线程 性能 JDK17 优化后,低竞争场景与 ReentrantLock 持平,高竞争场景略低 高竞争场景性能更稳定,可控性更强
2.2.3 读写锁 ReentrantReadWriteLock 适用于读多写少的场景,读锁是共享锁,读操作之间不互斥;写锁是排他锁,读和写、写和写之间互斥,大幅提升读多写少场景的并发性能。
写锁可降级为读锁(持有写锁的同时获取读锁,再释放写锁)
读锁不能升级为写锁(持有读锁时获取写锁会被永久阻塞)
读锁和写锁均支持可重入
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();
public Object get (String key) {
READ_LOCK.lock();
try {
return CACHE_MAP.get(key);
} finally {
READ_LOCK.unlock();
}
}
public void put (String key, Object value) {
WRITE_LOCK.lock();
try {
CACHE_MAP.put(key, value);
} finally {
WRITE_LOCK.unlock();
}
}
public void clear () {
WRITE_LOCK.lock();
try {
CACHE_MAP.clear();
} finally {
WRITE_LOCK.unlock();
}
}
}
2.2.4 StampedLock 邮戳锁 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 ();
public void write (int x, int y) {
long stamp = stampedLock.writeLock();
try {
this .x = x;
this .y = y;
} finally {
stampedLock.unlockWrite(stamp);
}
}
public int read () {
long stamp = stampedLock.tryOptimisticRead();
int currentX = this .x;
int currentY = this .y;
if (!stampedLock.validate(stamp)) {
stamp = stampedLock.readLock();
try {
currentX = this .x;
currentY = this .y;
} finally {
stampedLock.unlockRead(stamp);
}
}
return currentX + currentY;
}
}
2.3 AQS 核心原理,彻底搞懂锁的底层 AQS(AbstractQueuedSynchronizer,抽象队列同步器)是 JUC 锁和同步工具类的核心基础,ReentrantLock、CountDownLatch、Semaphore 等所有 JUC 同步工具均基于 AQS 实现。
通俗来讲:AQS 是一个并发编程的基础框架,用一个 volatile 修饰的 int 类型 state 变量表示同步状态,用一个双向链表(CLH 队列)管理等待获取锁的线程,提供了模板方法,子类只需实现 tryAcquire、tryRelease 等核心方法,即可实现自定义同步器。
2.3.1 AQS 核心结构
同步状态 state:volatile 修饰,保证多线程间的可见性,子类通过 getState()、setState()、compareAndSetState() 方法安全操作 state
独占锁:state=0 表示无锁,state>0 表示有线程持有锁,可重入时 state 递增
共享锁:state 表示可用的许可数量
CLH 等待队列:双向链表,存储等待获取锁的线程,节点分为独占模式和共享模式。线程获取锁失败时,会被封装成节点加入队列尾部,阻塞等待;锁释放时,会唤醒队列头部的线程
条件队列:Condition 接口实现,用于线程的等待和唤醒,一个 AQS 可对应多个条件队列,实现精准唤醒
2.3.2 基于 AQS 实现自定义独占锁 package com.jam.demo.lock;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
public class CustomAqsLock {
private static class Sync extends AbstractQueuedSynchronizer {
@Override
protected boolean tryAcquire (int arg) {
if (compareAndSetState(0 , 1 )) {
setExclusiveOwnerThread(Thread.currentThread());
return true ;
}
return false ;
}
@Override
protected boolean tryRelease (int arg) {
if (getExclusiveOwnerThread() != Thread.currentThread()) {
throw new IllegalMonitorStateException ("当前线程不是锁的持有者" );
}
setState(0 );
setExclusiveOwnerThread(null );
return true ;
}
@Override
protected boolean isHeldExclusively () {
return getState() == 1 ;
}
}
private final Sync sync = new Sync ();
public void lock () {
sync.acquire(1 );
}
public boolean tryLock () {
return sync.tryAcquire(1 );
}
public void unlock () {
sync.release(1 );
}
public boolean isLocked () {
return sync.isHeldExclusively();
}
}
三、JUC 核心工具类全解:生产环境高频使用的并发利器
3.1 原子类:无锁原子操作,解决复合操作原子性问题 原子类位于 java.util.concurrent.atomic 包下,底层基于 CAS+volatile 实现,无锁化,高并发场景下性能远高于锁。JDK17 中原子类分为四大类:
基本类型原子类:AtomicInteger、AtomicLong、AtomicBoolean
数组类型原子类:AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray
引用类型原子类:AtomicReference、AtomicStampedReference、AtomicMarkableReference
字段更新原子类:AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater
3.1.1 CAS 核心原理与问题解决 CAS(Compare And Swap,比较并交换)是 CPU 级别的原子指令,需要三个参数:内存地址 V、预期值 A、新值 B,只有当 V 的值等于 A 时,才会把 V 的值更新为 B,否则不做任何操作,返回当前值。
ABA 问题 :变量的值从 A 变成 B,又变回 A,CAS 会认为值没有变化,实际已发生修改。解决方案:AtomicStampedReference,给变量加上版本号,每次修改版本号递增,CAS 同时比较值和版本号
自旋开销大 :高并发场景下,大量线程同时 CAS,导致自旋重试次数过多,CPU 占用过高。解决方案:分段锁、LongAdder 替代 AtomicLong
单变量限制 :CAS 只能对单个变量进行原子操作,多变量原子性需用锁或 AtomicReference 封装多个变量
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 {
Thread thread1 = new Thread (() -> {
int stamp = reference.getStamp();
log.info("线程 1 初始版本号:{},初始值:{}" , stamp, reference.getReference());
reference.compareAndSet(100 , 200 , stamp, stamp + 1 );
reference.compareAndSet(200 , 100 , reference.getStamp(), reference.getStamp() + 1 );
log.info("线程 1 完成 ABA 操作,最终版本号:{},最终值:{}" , reference.getStamp(), reference.getReference());
});
Thread thread2 = new Thread (() -> {
int stamp = reference.getStamp();
log.info("线程 2 初始版本号:{},初始值:{}" , stamp, reference.getReference());
try {
Thread.sleep(1000 );
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.error("线程中断异常" , e);
}
boolean result = reference.compareAndSet(100 , 300 , stamp, stamp + 1 );
log.info("线程 2 更新结果:{},当前版本号:{}" , result, reference.getStamp());
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
}
}
3.1.2 JDK17 增强:VarHandle 变量句柄 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 ;
private static final VarHandle COUNT_HANDLE;
static {
try {
COUNT_HANDLE = MethodHandles.lookup().findVarHandle(VarHandleDemo.class, "count" , int .class);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new ExceptionInInitializerError (e);
}
}
public int incrementAndGet () {
int prev;
int next;
do {
prev = (int ) COUNT_HANDLE.getVolatile(this );
next = prev + 1 ;
} while (!COUNT_HANDLE.compareAndSet(this , prev, next));
return next;
}
public static void main (String[] args) throws InterruptedException {
VarHandleDemo demo = new VarHandleDemo ();
int threadCount = 10 ;
int incrementCount = 1000 ;
Thread[] threads = new Thread [threadCount];
for (int i = 0 ; i < threadCount; i++) {
threads[i] = new Thread (() -> {
for (int j = 0 ; j < incrementCount; j++) {
demo.incrementAndGet();
}
});
threads[i].start();
}
for (Thread thread : threads) {
thread.join();
}
log.info("最终计数结果:{}" , demo.count);
}
}
3.2 并发容器:高并发场景下的线程安全容器 日常开发中,很多开发者使用 Collections.synchronizedList() 等同步容器,但性能极低,因为所有方法都用 synchronized 修饰,同一时间只能有一个线程访问。JUC 提供了更高效的并发容器,针对高并发场景做了深度优化。
3.2.1 ConcurrentHashMap 高并发场景下的线程安全 HashMap,替代 HashMap 和 Hashtable。JDK17 的 ConcurrentHashMap 底层结构为数组 + 链表 + 红黑树,核心优化点:
读操作无锁:用 volatile 保证可见性,读操作无需加锁,性能极高
细粒度锁:JDK8 之后不再使用分段锁,改为对数组的每个桶节点加锁,锁粒度更细,并发度更高
多线程协助扩容:扩容时支持多线程协助,大幅提升扩容效率
容器 线程安全 锁机制 性能 适用场景 HashMap 否 无锁 最高 单线程场景 Hashtable 是 全表 synchronized 锁 极低 已废弃,不推荐使用 Collections.synchronizedMap 是 全表 synchronized 锁 低 低并发场景 ConcurrentHashMap 是 桶级细粒度锁+CAS 高 高并发读写场景
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 <>();
public void put (String key, Object value) {
if (!StringUtils.hasText(key) || ObjectUtils.isEmpty(value)) {
log.warn("缓存 key 或 value 为空" );
return ;
}
LOCAL_CACHE.put(key, value);
}
public Object get (String key) {
if (!StringUtils.hasText(key)) {
return null ;
}
return LOCAL_CACHE.get(key);
}
public Object putIfAbsent (String key, Object value) {
if (!StringUtils.hasText(key) || ObjectUtils.isEmpty(value)) {
return null ;
}
return LOCAL_CACHE.putIfAbsent(key, value);
}
public void remove (String key) {
if (!StringUtils.hasText(key)) {
return ;
}
LOCAL_CACHE.remove(key);
}
}
3.2.2 CopyOnWriteArrayList 高并发读多写少场景下的线程安全 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 {
private static final CopyOnWriteArrayList<String> WHITE_LIST = new CopyOnWriteArrayList <>();
public void addWhiteList (List<String> list) {
if (CollectionUtils.isEmpty(list)) {
return ;
}
WHITE_LIST.addAll(list);
}
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("127.0.0.1" , "192.168.1.1" ));
log.info("127.0.0.1 是否在白名单:{}" , demo.isInWhiteList("127.0.0.1" ));
log.info("10.0.0.1 是否在白名单:{}" , demo.isInWhiteList("10.0.0.1" ));
}
}
3.3 线程池:生产环境并发编程的核心,彻底搞懂原理与最佳实践 线程池是生产环境中使用最频繁的并发工具,也是最容易踩坑的。阿里巴巴 Java 开发手册明确禁止使用 Executors 创建线程池,必须通过 ThreadPoolExecutor 构造方法手动创建。
3.3.1 线程池的核心价值
降低资源消耗:重复利用已创建的线程,避免频繁创建和销毁线程的开销
提高响应速度:任务到达时,无需等待线程创建,直接执行
统一管理线程:控制线程的最大并发数,避免无限制创建线程导致 OOM,支持线程监控与调优
3.3.2 ThreadPoolExecutor 七大核心参数 public ThreadPoolExecutor (int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
corePoolSize 核心线程数 :线程池中长期存活的线程数量,即使空闲也不会被销毁(除非设置 allowCoreThreadTimeOut=true)
CPU 密集型任务:corePoolSize = CPU 核心数 + 1
IO 密集型任务:corePoolSize = CPU 核心数 * 2
混合型任务:拆分任务,分别设置线程池
maximumPoolSize 最大线程数 :线程池允许创建的最大线程数量,核心线程满、工作队列满后,才会创建新线程,直到达到最大值
keepAliveTime 空闲存活时间 :非核心线程的空闲存活时间,超过该时间会被销毁;设置 allowCoreThreadTimeOut=true 时,核心线程也受该时间控制
unit 时间单位 :keepAliveTime 的时间单位
workQueue 工作队列 :存储等待执行的任务,必须使用有界队列,生产环境禁止使用无界队列(会导致任务无限堆积,引发 OOM),推荐使用 ArrayBlockingQueue
threadFactory 线程工厂 :用于创建线程,生产环境必须自定义线程工厂,设置有意义的线程名称前缀,方便线上问题排查
handler 拒绝策略 :核心线程满、工作队列满、最大线程数满后,新任务会被拒绝,JDK 提供 4 种默认策略
AbortPolicy:默认策略,直接抛出 RejectedExecutionException,生产环境推荐使用,及时感知任务被拒绝
CallerRunsPolicy:用调用者线程执行任务,降低任务提交速度,起到流量控制作用
DiscardPolicy:直接丢弃任务,不抛出异常,不推荐使用
DiscardOldestPolicy:丢弃队列中最旧的任务,重新提交当前任务,不推荐使用
3.3.3 线程池执行流程
3.3.4 生产环境标准线程池实现 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 {
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 final int QUEUE_CAPACITY = 1000 ;
private static final String THREAD_NAME_PREFIX = "business-thread-pool-" ;
@Bean("businessThreadPool")
public ThreadPoolExecutor businessThreadPool () {
ThreadFactory threadFactory = new ThreadFactoryBuilder ()
.setNameFormat(THREAD_NAME_PREFIX + "%d" )
.setDaemon(false )
.build();
ThreadPoolExecutor threadPool = new ThreadPoolExecutor (
CORE_POOL_SIZE,
MAX_POOL_SIZE,
KEEP_ALIVE_TIME,
TimeUnit.SECONDS,
new ArrayBlockingQueue <>(QUEUE_CAPACITY),
threadFactory,
new ThreadPoolExecutor .AbortPolicy()
);
log.info("业务线程池初始化完成,核心线程数:{},最大线程数:{},队列容量:{}" ,
CORE_POOL_SIZE, MAX_POOL_SIZE, QUEUE_CAPACITY);
return threadPool;
}
}
3.3.5 JDK17 虚拟线程 虚拟线程是 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()) {
for (int i = 0 ; i < 10000 ; i++) {
int taskNum = i;
executor.submit(() -> {
log.info("虚拟线程执行任务:{}" , taskNum);
try {
Thread.sleep(100 );
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.error("任务执行中断" , e);
}
});
}
}
log.info("所有任务执行完成" );
}
}
3.4 同步工具类:多线程协作的核心利器 JUC 提供了丰富的同步工具类,用于多线程之间的协作,高频使用的有 CountDownLatch、CyclicBarrier、Semaphore。
3.4.1 CountDownLatch 闭锁 一次性同步工具,允许一个或多个线程等待其他线程完成操作后再执行,基于 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 {
countDownLatch.countDown();
}
}).start();
}
log.info("主线程等待所有任务执行完成" );
countDownLatch.await();
log.info("所有任务执行完成,主线程继续执行" );
}
}
3.4.2 CyclicBarrier 循环栅栏 可重复使用的同步工具,允许一组线程互相等待,直到所有线程都到达栅栏位置,然后一起执行后续操作。基于 ReentrantLock 和 Condition 实现,计数变为 0 后会自动重置,可重复使用。
适用场景:多线程并行计算,每个阶段都需要等待所有线程完成后再进入下一个阶段,比如压力测试,多个线程同时启动执行压测。
CountDownLatch 与 CyclicBarrier 核心区别
特性 CountDownLatch CyclicBarrier 可重用性 一次性,计数变为 0 后无法重置 可重复使用,计数变为 0 后自动重置 等待对象 主线程等待多个工作线程完成 多个工作线程互相等待,全部到达后一起执行 计数方式 线程调用 countDown() 减 1,不阻塞 线程调用 await() 减 1,阻塞等待 高级特性 无 支持传入 Runnable 任务,所有线程到达后优先执行
3.4.3 Semaphore 信号量 用于控制同时访问特定资源的线程数量,实现流量控制,基于 AQS 实现,用 state 变量表示可用的许可数量,acquire() 方法获取许可,release() 方法释放许可。
适用场景:流量控制,比如数据库连接池、接口限流、秒杀场景的并发控制。
package com.jam.demo.sync;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.Semaphore;
@Slf4j
public class SemaphoreLimitDemo {
private static final int MAX_CONCURRENT = 10 ;
private static final Semaphore semaphore = new Semaphore (MAX_CONCURRENT);
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 SemaphoreLimitDemo ();
for (int i = 0 ; i < 100 ; i++) {
String requestId = "REQ-" + i;
new Thread (() -> demo.handleRequest(requestId)).start();
}
}
}
四、生产级并发实战:商品库存扣减场景,彻底解决超卖问题
4.1 业务场景与环境准备 电商秒杀场景中,用户下单扣减商品库存,高并发场景下极易出现超卖问题(库存扣减为负数)。本文基于 JDK17、SpringBoot3、MyBatis-Plus、MySQL8.0,通过多种方案解决超卖问题,所有代码均可直接编译运行。
4.1.1 Maven 核心依赖 <?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 >
<java.version > 17</java.version >
<mybatis-plus.version > 3.5.7</mybatis-plus.version >
<lombok.version > 1.18.32</lombok.version >
<fastjson2.version > 2.0.52</fastjson2.version >
<guava.version > 33.2.0-jre</guava.version >
<springdoc.version > 2.5.0</springdoc.version >
</properties >
<dependencies >
<dependency >
<groupId > org.springframework.boot</groupId >
<artifactId > spring-boot-starter-web</artifactId >
</dependency >
<dependency >
<groupId > org.springframework.boot</groupId >
<artifactId > spring-boot-starter-jdbc</artifactId >
</dependency >
<dependency >
<groupId > com.baomidou</groupId >
<artifactId > mybatis-plus-boot-starter</artifactId >
<version > ${mybatis-plus.version}</version >
</dependency >
<dependency >
<groupId > com.mysql</groupId >
<artifactId > mysql-connector-j</artifactId >
<scope > runtime</scope >
</dependency >
<dependency >
<groupId > org.projectlombok</groupId >
<artifactId > lombok</artifactId >
<version > ${lombok.version}</version >
<scope > provided</scope >
</dependency >
<dependency >
<groupId > com.alibaba.fastjson2</groupId >
<artifactId > fastjson2</artifactId >
<version > ${fastjson2.version}</version >
</dependency >
<dependency >
<groupId > com.google.guava</groupId >
<artifactId > guava</artifactId >
<version > ${guava.version}</version >
</dependency >
<dependency >
<groupId > org.springdoc</groupId >
<artifactId > springdoc-openapi-starter-webmvc-ui</artifactId >
<version > ${springdoc.version}</version >
</dependency >
<dependency >
<groupId > org.springframework.boot</groupId >
<artifactId > spring-boot-starter-test</artifactId >
<scope > test</scope >
</dependency >
</dependencies >
<build >
<plugins >
<plugin >
<groupId > org.springframework.boot</groupId >
<artifactId > spring-boot-maven-plugin</artifactId >
<configuration >
<excludes >
<exclude >
<groupId > org.projectlombok</groupId >
<artifactId > lombok</artifactId >
</exclude >
</excludes >
</configuration >
</plugin >
</plugins >
</build >
</project >
4.1.2 MySQL 表结构 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= '商品库存表' ;
4.1.3 实体类 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;
}
4.1.4 Mapper 接口 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> {
@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) ;
}
4.2 错误实现:无锁直接扣减,导致超卖 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;
@Transactional(rollbackFor = Exception.class)
public boolean deductStock (Long productId, Integer num) {
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 ;
}
if (productStock.getStockNum() < num) {
log.error("商品库存不足,productId:{},当前库存:{},扣减数量:{}" , productId, productStock.getStockNum(), num);
return false ;
}
productStock.setStockNum(productStock.getStockNum() - num);
productStockMapper.updateById(productStock);
log.info("商品库存扣减成功,productId:{},扣减后库存:{}" , productId, productStock.getStockNum());
return true ;
}
}
问题分析 :高并发场景下,多个线程同时查询到库存充足,同时执行扣减,导致库存扣减为负数,出现超卖。
4.3 正确实现 1:悲观锁方案 基于 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;
public boolean deductStock (Long productId, Integer num) {
DefaultTransactionDefinition definition = new DefaultTransactionDefinition ();
definition.setPropagationBehavior(DefaultTransactionDefinition.PROPAGATION_REQUIRED);
TransactionStatus status = transactionManager.getTransaction(definition);
try {
LambdaQueryWrapper<ProductStock> queryWrapper = new LambdaQueryWrapper <ProductStock>()
.eq(ProductStock::getProductId, productId)
.last("FOR UPDATE" );
ProductStock productStock = productStockMapper.selectOne(queryWrapper);
if (ObjectUtils.isEmpty(productStock)) {
log.error("商品库存不存在,productId:{}" , productId);
transactionManager.rollback(status);
return false ;
}
if (productStock.getStockNum() < num) {
log.error("商品库存不足,productId:{},当前库存:{},扣减数量:{}" , productId, productStock.getStockNum(), num);
transactionManager.rollback(status);
return false ;
}
productStock.setStockNum(productStock.getStockNum() - num);
productStockMapper.updateById(productStock);
transactionManager.commit(status);
log.info("商品库存扣减成功,productId:{},扣减后库存:{}" , productId, productStock.getStockNum());
return true ;
} catch (Exception e) {
transactionManager.rollback(status);
log.error("商品库存扣减异常,productId:{}" , productId, e);
return false ;
}
}
}
4.4 正确实现 2:乐观锁方案 基于版本号机制实现,MyBatis-Plus 内置乐观锁插件,更新时校验版本号,版本号匹配才更新成功,否则更新失败,避免超卖,性能高于悲观锁,适合读多写少的场景。
4.4.1 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 {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor () {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor ();
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor ());
interceptor.addInnerInterceptor(new PaginationInnerInterceptor (DbType.MYSQL));
return interceptor;
}
}
4.4.2 乐观锁业务实现 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 ;
public boolean deductStock (Long productId, Integer num) {
int retryCount = 0 ;
while (retryCount < MAX_RETRY) {
DefaultTransactionDefinition definition = new DefaultTransactionDefinition ();
definition.setPropagationBehavior(DefaultTransactionDefinition.PROPAGATION_REQUIRED);
TransactionStatus status = transactionManager.getTransaction(definition);
try {
LambdaQueryWrapper<ProductStock> queryWrapper = new LambdaQueryWrapper <ProductStock>()
.eq(ProductStock::getProductId, productId);
ProductStock productStock = productStockMapper.selectOne(queryWrapper);
if (ObjectUtils.isEmpty(productStock)) {
log.error("商品库存不存在,productId:{}" , productId);
transactionManager.rollback(status);
return false ;
}
if (productStock.getStockNum() < num) {
log.error("商品库存不足,productId:{},当前库存:{},扣减数量:{}" , productId, productStock.getStockNum(), num);
transactionManager.rollback(status);
return false ;
}
productStock.setStockNum(productStock.getStockNum() - num);
int updateCount = productStockMapper.updateById(productStock);
if (updateCount > 0 ) {
transactionManager.commit(status);
log.info("商品库存扣减成功,productId:{},扣减后库存:{},重试次数:{}" ,
productId, productStock.getStockNum(), retryCount);
return true ;
}
transactionManager.rollback(status);
retryCount++;
Thread.sleep(10 );
} catch (Exception e) {
transactionManager.rollback(status);
log.error("商品库存扣减异常,productId:{}" , productId, e);
return false ;
}
}
log.error("商品库存扣减重试次数超过上限,productId:{}" , productId);
return false ;
}
}
4.5 正确实现 3:SQL 直接扣减,数据库层面保证原子性 直接通过 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;
public boolean deductStock (Long productId, Integer num) {
DefaultTransactionDefinition definition = new DefaultTransactionDefinition ();
definition.setPropagationBehavior(DefaultTransactionDefinition.PROPAGATION_REQUIRED);
TransactionStatus status = transactionManager.getTransaction(definition);
try {
int updateCount = productStockMapper.deductStock(productId, num);
if (updateCount > 0 ) {
transactionManager.commit(status);
log.info("商品库存扣减成功,productId:{}" , productId);
return true ;
}
transactionManager.rollback(status);
log.error("商品库存扣减失败,库存不足或商品不存在,productId:{}" , productId);
return false ;
} catch (Exception e) {
transactionManager.rollback(status);
log.error("商品库存扣减异常,productId:{}" , productId, e);
return false ;
}
}
}
五、并发编程常见坑点与线上问题排查指南
5.1 高频踩坑点与避坑指南
5.1.1 死锁问题 死锁是指两个或多个线程互相等待对方持有的锁,导致永久阻塞的现象。死锁的四个必要条件,缺一不可,破坏其中一个即可避免死锁:
互斥条件:一个资源同一时间只能被一个线程持有
持有并等待条件:线程持有至少一个资源,又请求其他线程持有的资源,同时不释放自己持有的资源
不可剥夺条件:线程持有的资源,只能自己释放,不能被其他线程强行剥夺
循环等待条件:多个线程之间形成循环等待资源的链
固定加锁顺序,所有线程按照相同的顺序获取锁
避免持有锁的同时等待其他锁,尽量减少锁的持有时间
使用 tryLock() 方法设置超时时间,避免无限等待
尽量使用细粒度的锁,减少锁的范围
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 ();
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(1000 );
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.error("线程中断" , e);
}
log.info("线程 2 等待获取 LOCK_A" );
synchronized (LOCK_A) {
log.info("线程 2 获取到 LOCK_A" );
}
}
}, "dead-lock-thread-2" ).start();
}
public static void fixDeadLockDemo () {
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" );
}
}
}, "fix-thread-1" ).start();
new Thread (() -> {
synchronized (LOCK_A) {
log.info("线程 2 获取到 LOCK_A" );
try {
Thread.sleep(1000 );
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.error("线程中断" , e);
}
log.info("线程 2 等待获取 LOCK_B" );
synchronized (LOCK_B) {
log.info("线程 2 获取到 LOCK_B" );
}
}
}, "fix-thread-2" ).start();
}
public static void main (String[] args) {
deadLockDemo();
}
}
5.1.2 线程池常见坑
使用无界队列,导致任务无限堆积,引发 OOM
最大线程数设置过大,导致大量线程创建,CPU 占用 100%
线程池定义在方法内部,每次调用方法都创建新的线程池,导致线程无限创建
线程池中的任务捕获异常不当,导致异常被吞,无法感知任务执行失败
5.1.3 线程安全常见坑
使用 SimpleDateFormat,该类不是线程安全的,多线程场景下会出现异常,推荐使用 java.time 包下的 DateTimeFormatter
多线程场景下修改成员变量,未做线程安全控制,导致数据不一致,尽量使用局部变量(线程私有)
迭代集合的过程中修改集合,抛出 ConcurrentModificationException,推荐使用并发容器
5.1.4 volatile 误用
用 volatile 修饰复合操作的变量(如 i++),volatile 只能保证可见性和有序性,不能保证原子性
双重检查锁单例未加 volatile,导致指令重排引发空指针异常
5.2 线上并发问题排查工具与方法
5.2.1 JDK 自带排查工具
jps :查看 Java 进程的 PID,是所有排查的第一步
jstack :查看线程的堆栈信息,自动检测死锁,排查线程阻塞、死循环等问题
jstat :查看 JVM 的运行状态,GC 情况、内存使用情况
jmap :查看堆内存信息,生成堆转储文件,排查 OOM 问题
JConsole/VisualVM :可视化工具,监控 JVM 运行状态、线程状态、内存使用情况
5.2.2 死锁排查步骤
用 jps -l 获取 Java 进程的 PID
用 jstack <PID> 查看线程堆栈信息,jstack 会自动检测死锁,输出死锁的线程、持有的锁、等待的锁
根据堆栈信息,定位到代码中的死锁位置,按照避坑方法修复
5.2.3 线程安全问题排查步骤
复现问题,确定问题出现的场景和触发条件
检查共享变量的操作,是否保证了原子性、可见性、有序性
检查锁的使用是否正确,锁的范围是否合理,是否存在锁顺序问题
用日志、Arthas 等工具,打印变量的修改过程,定位问题代码
六、总结 Java 并发编程的核心,不是背会 API 和面试题,而是理解底层的 JMM 内存模型、锁的实现原理、AQS 的核心逻辑。只有搞懂了底层原理,才能在实际项目中正确使用并发工具,避免踩坑,快速定位和解决线上问题。
相关免费在线工具 Keycode 信息 查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
Escape 与 Native 编解码 JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
JavaScript / HTML 格式化 使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
JavaScript 压缩与混淆 Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
加密/解密文本 使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
Gemini 图片去水印 基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online