跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
Javajava算法

Java 并发编程核心体系:从 JMM 原理到生产实战

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

蜜桃汽水发布于 2026/3/26更新于 2026/5/2327 浏览
Java 并发编程核心体系:从 JMM 原理到生产实战

一、并发编程的核心基石: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;

    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 = 0; j < INCREMENT_COUNT; j++) {
                        count.incrementAndGet();
                    }
                } finally {
                    countDownLatch.countDown();
                }
            }).start();
        }
        countDownLatch.await();
        // 预期结果 10000,实际结果始终等于 10000
        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) {
                // 空循环,线程不会重新读取主内存的 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 修饰的变量,写操作后会插入写屏障,强制把工作内存的最新值刷新到主内存;读操作前会插入读屏障,强制从主内存读取最新值,保证每次读取的都是最新的。

1.3.3 有序性

程序执行的顺序按照代码的先后顺序执行,编译器和 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;
    }
}
1.4 happens-before 核心规则

happens-before 是 JMM 的核心,用来判断多线程场景下是否存在数据竞争、是否线程安全,它不是指 A 操作在 B 操作之前执行,而是说 A 操作的执行结果对 B 操作可见,保证多线程场景下的可见性和有序性。JSR-133 定义了 8 条核心规则:

  1. 程序次序规则:一个线程内,按照代码顺序,前面的操作 happens-before 于后面的操作
  2. 锁规则:一个 unlock 操作 happens-before 于后续对同一个锁的 lock 操作
  3. volatile 变量规则:对一个 volatile 变量的写操作 happens-before 于后续对这个变量的读操作
  4. 线程启动规则:Thread 的 start() 方法 happens-before 于该线程内的所有操作
  5. 线程终止规则:线程内的所有操作 happens-before 于其他线程检测到该线程终止
  6. 线程中断规则:对线程 interrupt() 方法的调用 happens-before 于被中断线程的代码检测到中断事件的发生
  7. 对象终结规则:一个对象的初始化完成 happens-before 于它的 finalize() 方法的开始
  8. 传递性规则:如果 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++;
    }

    /**
     * 静态同步方法,锁当前类的 Class 对象
     */
    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++) {
                        // 同步代码块,锁 demo 对象
                        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;
    // 创建非公平锁,传入 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++) {
            new Thread(() -> {
                try {
                    for (int j = 0; j < INCREMENT_COUNT; j++) {
                        // 获取锁
                        lock.lock();
                        try {
                            count++;
                        } finally {
                            // 释放锁必须放在 finally 块中
                            lock.unlock();
                        }
                    }
                } finally {
                    countDownLatch.countDown();
                }
            }).start();
        }
        countDownLatch.await();
        log.info("最终计数结果:{}", count);
    }
}
2.2.2 synchronized 与 ReentrantLock 核心区别
特性synchronizedReentrantLock
锁实现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();

    /**
     * 从缓存获取数据
     * @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
     * @param value 缓存 value
     */
    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();

    /**
     * 写操作,加写锁
     * @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;
        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 核心结构

文章配图

  1. 同步状态 state:volatile 修饰,保证多线程间的可见性,子类通过 getState()、setState()、compareAndSetState() 方法安全操作 state
    • 独占锁:state=0 表示无锁,state>0 表示有线程持有锁,可重入时 state 递增
    • 共享锁:state 表示可用的许可数量
  2. CLH 等待队列:双向链表,存储等待获取锁的线程,节点分为独占模式和共享模式。线程获取锁失败时,会被封装成节点加入队列尾部,阻塞等待;锁释放时,会唤醒队列头部的线程
  3. 条件队列:Condition 接口实现,用于线程的等待和唤醒,一个 AQS 可对应多个条件队列,实现精准唤醒
2.3.2 基于 AQS 实现自定义独占锁
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(0);
            setExclusiveOwnerThread(null);
            return true;
        }

        /**
         * 判断是否持有锁
         * @return 是否持有锁
         */
        @Override
        protected boolean isHeldExclusively() {
            return getState() == 1;
        }
    }

    private final Sync sync = new Sync();

    /**
     * 获取锁,阻塞直到获取成功
     */
    public void lock() {
        sync.acquire(1);
    }

    /**
     * 尝试获取锁,非阻塞,立即返回结果
     * @return 是否获取成功
     */
    public boolean tryLock() {
        return sync.tryAcquire(1);
    }

    /**
     * 释放锁
     */
    public void unlock() {
        sync.release(1);
    }

    /**
     * 判断锁是否被持有
     * @return 是否被持有
     */
    public boolean isLocked() {
        return sync.isHeldExclusively();
    }
}

三、JUC 核心工具类全解:生产环境高频使用的并发利器

3.1 原子类:无锁原子操作,解决复合操作原子性问题

原子类位于 java.util.concurrent.atomic 包下,底层基于 CAS+volatile 实现,无锁化,高并发场景下性能远高于锁。JDK17 中原子类分为四大类:

  1. 基本类型原子类:AtomicInteger、AtomicLong、AtomicBoolean
  2. 数组类型原子类:AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray
  3. 引用类型原子类:AtomicReference、AtomicStampedReference、AtomicMarkableReference
  4. 字段更新原子类:AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater
3.1.1 CAS 核心原理与问题解决

CAS(Compare And Swap,比较并交换)是 CPU 级别的原子指令,需要三个参数:内存地址 V、预期值 A、新值 B,只有当 V 的值等于 A 时,才会把 V 的值更新为 B,否则不做任何操作,返回当前值。

CAS 三大核心问题与解决方案:

  1. ABA 问题:变量的值从 A 变成 B,又变回 A,CAS 会认为值没有变化,实际已发生修改。解决方案:AtomicStampedReference,给变量加上版本号,每次修改版本号递增,CAS 同时比较值和版本号
  2. 自旋开销大:高并发场景下,大量线程同时 CAS,导致自旋重试次数过多,CPU 占用过高。解决方案:分段锁、LongAdder 替代 AtomicLong
  3. 单变量限制: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 {
        // 线程 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 stamp = reference.getStamp();
            log.info("线程 2 初始版本号:{},初始值:{}", stamp, reference.getReference());
            try {
                // 等待线程 1 完成 ABA 操作
                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;
    // 定义 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 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高高并发读写场景

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 null;
        }
        return LOCAL_CACHE.putIfAbsent(key, value);
    }

    /**
     * 删除缓存
     * @param key 缓存 key
     */
    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 {
    // 初始化 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("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)
  1. corePoolSize 核心线程数:线程池中长期存活的线程数量,即使空闲也不会被销毁(除非设置 allowCoreThreadTimeOut=true)
    • CPU 密集型任务:corePoolSize = CPU 核心数 + 1
    • IO 密集型任务:corePoolSize = CPU 核心数 * 2
    • 混合型任务:拆分任务,分别设置线程池
  2. maximumPoolSize 最大线程数:线程池允许创建的最大线程数量,核心线程满、工作队列满后,才会创建新线程,直到达到最大值
  3. keepAliveTime 空闲存活时间:非核心线程的空闲存活时间,超过该时间会被销毁;设置 allowCoreThreadTimeOut=true 时,核心线程也受该时间控制
  4. unit 时间单位:keepAliveTime 的时间单位
  5. workQueue 工作队列:存储等待执行的任务,必须使用有界队列,生产环境禁止使用无界队列(会导致任务无限堆积,引发 OOM),推荐使用 ArrayBlockingQueue
  6. threadFactory 线程工厂:用于创建线程,生产环境必须自定义线程工厂,设置有意义的线程名称前缀,方便线上问题排查
  7. 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 {
    /**
     * 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 final int QUEUE_CAPACITY = 1000;
    /**
     * 线程名称前缀
     */
    private static final String THREAD_NAME_PREFIX = "business-thread-pool-";

    /**
     * 业务通用线程池
     * @return ThreadPoolExecutor
     */
    @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()) {
            // 提交 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("所有任务执行完成");
    }
}
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 {
                    // 计数减 1
                    countDownLatch.countDown();
                }
            }).start();
        }
        // 主线程阻塞,等待所有任务完成
        log.info("主线程等待所有任务执行完成");
        countDownLatch.await();
        log.info("所有任务执行完成,主线程继续执行");
    }
}
3.4.2 CyclicBarrier 循环栅栏

可重复使用的同步工具,允许一组线程互相等待,直到所有线程都到达栅栏位置,然后一起执行后续操作。基于 ReentrantLock 和 Condition 实现,计数变为 0 后会自动重置,可重复使用。

适用场景:多线程并行计算,每个阶段都需要等待所有线程完成后再进入下一个阶段,比如压力测试,多个线程同时启动执行压测。

CountDownLatch 与 CyclicBarrier 核心区别

特性CountDownLatchCyclicBarrier
可重用性一次性,计数变为 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 {
    // 最大并发数 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 SemaphoreLimitDemo();
        // 模拟 100 个并发请求
        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> {
    /**
     * 悲观锁扣减库存
     * @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);
}
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;

    /**
     * 错误扣减实现,无锁控制,高并发下超卖
     * @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 false;
        }
        // 3.扣减库存
        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;

    /**
     * 悲观锁扣减库存,编程式事务控制
     * @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 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;
            }
            // 2.校验库存
            if (productStock.getStockNum() < num) {
                log.error("商品库存不足,productId:{},当前库存:{},扣减数量:{}", productId, productStock.getStockNum(), num);
                transactionManager.rollback(status);
                return false;
            }
            // 3.扣减库存
            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 {
    /**
     * 配置 MyBatis-Plus 拦截器
     * @return MybatisPlusInterceptor
     */
    @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;

    /**
     * 乐观锁扣减库存,带重试机制
     * @param productId 商品 ID
     * @param num 扣减数量
     * @return 扣减结果
     */
    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 {
                // 1.查询库存
                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;
                }
                // 2.校验库存
                if (productStock.getStockNum() < num) {
                    log.error("商品库存不足,productId:{},当前库存:{},扣减数量:{}", productId, productStock.getStockNum(), num);
                    transactionManager.rollback(status);
                    return false;
                }
                // 3.扣减库存,乐观锁自动校验版本号
                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;

    /**
     * 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:{}", 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 死锁问题

死锁是指两个或多个线程互相等待对方持有的锁,导致永久阻塞的现象。死锁的四个必要条件,缺一不可,破坏其中一个即可避免死锁:

  1. 互斥条件:一个资源同一时间只能被一个线程持有
  2. 持有并等待条件:线程持有至少一个资源,又请求其他线程持有的资源,同时不释放自己持有的资源
  3. 不可剥夺条件:线程持有的资源,只能自己释放,不能被其他线程强行剥夺
  4. 循环等待条件:多个线程之间形成循环等待资源的链

避坑方法:

  • 固定加锁顺序,所有线程按照相同的顺序获取锁
  • 避免持有锁的同时等待其他锁,尽量减少锁的持有时间
  • 使用 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();

    /**
     * 死锁示例:线程 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(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();
    }

    /**
     * 修复死锁:固定加锁顺序,所有线程先获取 LOCK_A,再获取 LOCK_B
     */
    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();
        // 执行修复后的示例
        // fixDeadLockDemo();
    }
}
5.1.2 线程池常见坑
  1. 使用无界队列,导致任务无限堆积,引发 OOM
  2. 最大线程数设置过大,导致大量线程创建,CPU 占用 100%
  3. 线程池定义在方法内部,每次调用方法都创建新的线程池,导致线程无限创建
  4. 线程池中的任务捕获异常不当,导致异常被吞,无法感知任务执行失败
5.1.3 线程安全常见坑
  1. 使用 SimpleDateFormat,该类不是线程安全的,多线程场景下会出现异常,推荐使用 java.time 包下的 DateTimeFormatter
  2. 多线程场景下修改成员变量,未做线程安全控制,导致数据不一致,尽量使用局部变量(线程私有)
  3. 迭代集合的过程中修改集合,抛出 ConcurrentModificationException,推荐使用并发容器
5.1.4 volatile 误用
  1. 用 volatile 修饰复合操作的变量(如 i++),volatile 只能保证可见性和有序性,不能保证原子性
  2. 双重检查锁单例未加 volatile,导致指令重排引发空指针异常
5.2 线上并发问题排查工具与方法
5.2.1 JDK 自带排查工具
  1. jps:查看 Java 进程的 PID,是所有排查的第一步
  2. jstack:查看线程的堆栈信息,自动检测死锁,排查线程阻塞、死循环等问题
  3. jstat:查看 JVM 的运行状态,GC 情况、内存使用情况
  4. jmap:查看堆内存信息,生成堆转储文件,排查 OOM 问题
  5. JConsole/VisualVM:可视化工具,监控 JVM 运行状态、线程状态、内存使用情况
5.2.2 死锁排查步骤
  1. 用 jps -l 获取 Java 进程的 PID
  2. 用 jstack <PID> 查看线程堆栈信息,jstack 会自动检测死锁,输出死锁的线程、持有的锁、等待的锁
  3. 根据堆栈信息,定位到代码中的死锁位置,按照避坑方法修复
5.2.3 线程安全问题排查步骤
  1. 复现问题,确定问题出现的场景和触发条件
  2. 检查共享变量的操作,是否保证了原子性、可见性、有序性
  3. 检查锁的使用是否正确,锁的范围是否合理,是否存在锁顺序问题
  4. 用日志、Arthas 等工具,打印变量的修改过程,定位问题代码

六、总结

Java 并发编程的核心,不是背会 API 和面试题,而是理解底层的 JMM 内存模型、锁的实现原理、AQS 的核心逻辑。只有搞懂了底层原理,才能在实际项目中正确使用并发工具,避免踩坑,快速定位和解决线上问题。

目录

  1. 一、并发编程的核心基石:JMM 内存模型与三大特性
  2. 1.1 为什么需要 JMM 内存模型
  3. 1.2 JMM 核心结构
  4. 1.3 并发编程三大核心特性
  5. 1.3.1 原子性
  6. 1.3.2 可见性
  7. 1.3.3 有序性
  8. 1.4 happens-before 核心规则
  9. 二、并发编程的核心锁机制:从底层原理到实战选型
  10. 2.1 synchronized 底层原理与 JDK17 优化
  11. 2.1.1 对象头与锁状态
  12. 2.1.2 锁升级流程
  13. 2.1.3 synchronized 正确使用示例
  14. 2.2 Lock 体系核心实现与实战选型
  15. 2.2.1 ReentrantLock 核心使用
  16. 2.2.2 synchronized 与 ReentrantLock 核心区别
  17. 2.2.3 读写锁 ReentrantReadWriteLock
  18. 2.2.4 StampedLock 邮戳锁
  19. 2.3 AQS 核心原理,彻底搞懂锁的底层
  20. 2.3.1 AQS 核心结构
  21. 2.3.2 基于 AQS 实现自定义独占锁
  22. 三、JUC 核心工具类全解:生产环境高频使用的并发利器
  23. 3.1 原子类:无锁原子操作,解决复合操作原子性问题
  24. 3.1.1 CAS 核心原理与问题解决
  25. 3.1.2 JDK17 增强:VarHandle 变量句柄
  26. 3.2 并发容器:高并发场景下的线程安全容器
  27. 3.2.1 ConcurrentHashMap
  28. 3.2.2 CopyOnWriteArrayList
  29. 3.3 线程池:生产环境并发编程的核心,彻底搞懂原理与最佳实践
  30. 3.3.1 线程池的核心价值
  31. 3.3.2 ThreadPoolExecutor 七大核心参数
  32. 3.3.3 线程池执行流程
  33. 3.3.4 生产环境标准线程池实现
  34. 3.3.5 JDK17 虚拟线程
  35. 3.4 同步工具类:多线程协作的核心利器
  36. 3.4.1 CountDownLatch 闭锁
  37. 3.4.2 CyclicBarrier 循环栅栏
  38. 3.4.3 Semaphore 信号量
  39. 四、生产级并发实战:商品库存扣减场景,彻底解决超卖问题
  40. 4.1 业务场景与环境准备
  41. 4.1.1 Maven 核心依赖
  42. 4.1.2 MySQL 表结构
  43. 4.1.3 实体类
  44. 4.1.4 Mapper 接口
  45. 4.2 错误实现:无锁直接扣减,导致超卖
  46. 4.3 正确实现 1:悲观锁方案
  47. 4.4 正确实现 2:乐观锁方案
  48. 4.4.1 MyBatis-Plus 乐观锁配置
  49. 4.4.2 乐观锁业务实现
  50. 4.5 正确实现 3:SQL 直接扣减,数据库层面保证原子性
  51. 五、并发编程常见坑点与线上问题排查指南
  52. 5.1 高频踩坑点与避坑指南
  53. 5.1.1 死锁问题
  54. 5.1.2 线程池常见坑
  55. 5.1.3 线程安全常见坑
  56. 5.1.4 volatile 误用
  57. 5.2 线上并发问题排查工具与方法
  58. 5.2.1 JDK 自带排查工具
  59. 5.2.2 死锁排查步骤
  60. 5.2.3 线程安全问题排查步骤
  61. 六、总结
  • 💰 8折买阿里云服务器限时8折了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • Coze 低代码 AI 智能体平台:100 个落地场景与发布指南
  • Flutter wallet_connect 鸿蒙适配:Web3 钱包与 DApp 授权
  • Ubuntu 22.04 下编译安装 libwebkit2gtk-4.1-0 指南
  • C++ 性能优化:提升代码执行效率的核心技巧
  • 基于火山引擎即梦 API 的数字人视频生成示例
  • VSCode + GitHub Copilot AI 编程实战教程
  • C++ 在线判题系统(OJ)设计与实现
  • Apache Airflow 与 Quartz 调度框架对比及选型指南
  • 本地部署 Wan2.1 视频生成模型及远程访问配置
  • 二叉树深度计算与先序序列重构算法实战
  • RTD1296PB 与 RK3568:NAS 及智能家居芯片实战性能对比
  • Flask 集成 flask-admin 后台及 flask-login 权限管理
  • 基于DeepSeek与Cursor构建智能代码审查工具实战
  • 消息队列理论基础与 Kafka 架构价值解析
  • OpenClaw Windows 安装配置教程:Node.js 22、Kimi 模型与飞书机器人集成
  • C++ 类与对象实战:从零实现日期类
  • 基于腾讯云轻量应用服务器部署 OpenClaw 并接入 QQ 与飞书机器人
  • 基于纯 Verilog FPGA 的双线性插值视频缩放设计
  • JavaScript 反混淆实战:Obfuscator.io 工具解析
  • cann-recipes-train 实战:昇腾 DeepSeek-R1 与 Qwen2.5 强化学习优化

相关免费在线工具

  • 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