欢迎光临
我们一直在努力

别再只会用 synchronized!Java 并发编程全链路核心体系,从底层原理到生产实战全覆盖

很多Java开发者在面试时能背出并发编程的核心概念,却在实际项目中频繁踩坑:出现超卖、死锁、数据不一致等线上问题时无从下手;只会用synchronized加锁,却不懂底层原理导致性能瓶颈;乱用线程池引发OOM。本文基于JDK 17,从JMM内存模型底层原理,到锁机制、JUC核心工具,再到生产级实战与避坑指南,全链路讲透Java并发编程的核心知识点,兼顾理论深度与落地实用性,帮你彻底打通并发编程的任督二脉。

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

/**
 * 原子性错误示例
 * @author ken
 */
@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;

/**
 * 原子性正确示例
 * @author ken
 */
@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;

/**
 * 可见性错误示例
 * @author ken
 */
@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;

/**
 * 可见性正确示例
 * @author ken
 */
@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;

/**
 * 双重检查锁单例模式
 * @author ken
 */
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条核心规则:

  • 程序次序规则:一个线程内,按照代码顺序,前面的操作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;

    /**
     * synchronized使用示例
     * @author ken
     */
    @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;

    /**
     * ReentrantLock使用示例
     * @author ken
     */
    @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;

    /**
     * 读写锁实现缓存示例
     * @author ken
     */
    @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;

    /**
     * StampedLock使用示例
     * @author ken
     */
    @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核心结构

  • 同步状态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;

    /**
     * 基于AQS实现自定义独占锁
     * @author ken
     */
    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中原子类分为四大类:

  • 基本类型原子类: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,否则不做任何操作,返回当前值。

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

  • 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;

    /**
     * AtomicStampedReference解决ABA问题
     * @author ken
     */
    @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;

    /**
     * VarHandle使用示例
     * @author ken
     */
    @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;

    /**
     * ConcurrentHashMap实现本地缓存
     * @author ken
     */
    @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;

    /**
     * CopyOnWriteArrayList使用示例
     * @author ken
     */
    @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)

  • 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;

    /**
     * 生产环境线程池配置
     * @author ken
     */
    @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;

    /**
     * 虚拟线程使用示例
     * JDK17需添加VM参数:–enable-preview
     * @author ken
     */
    @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;

    /**
     * CountDownLatch使用示例
     * @author ken
     */
    @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;

    /**
     * Semaphore实现接口限流
     * @author ken
     */
    @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;

    /**
     * 商品库存实体类
     * @author ken
     */
    @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;

    /**
     * 商品库存Mapper接口
     * @author ken
     */
    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;

    /**
     * 错误库存扣减实现
     * @author ken
     */
    @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;

    /**
     * 悲观锁库存扣减实现
     * @author ken
     */
    @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;

    /**
     * MyBatis-Plus配置类
     * @author ken
     */
    @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;

    /**
     * 乐观锁库存扣减实现
     * @author ken
     */
    @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;

    /**
     * SQL原子扣减库存实现
     * @author ken
     */
    @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 死锁问题

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

  • 互斥条件:一个资源同一时间只能被一个线程持有

  • 持有并等待条件:线程持有至少一个资源,又请求其他线程持有的资源,同时不释放自己持有的资源

  • 不可剥夺条件:线程持有的资源,只能自己释放,不能被其他线程强行剥夺

  • 循环等待条件:多个线程之间形成循环等待资源的链

  • 避坑方法:

    • 固定加锁顺序,所有线程按照相同的顺序获取锁

    • 避免持有锁的同时等待其他锁,尽量减少锁的持有时间

    • 使用tryLock()方法设置超时时间,避免无限等待

    • 尽量使用细粒度的锁,减少锁的范围

    死锁示例与修复

    package com.jam.demo.deadlock;

    import lombok.extern.slf4j.Slf4j;

    /**
     * 死锁示例与修复
     * @author ken
     */
    @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 线程池常见坑
  • 使用无界队列,导致任务无限堆积,引发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的核心逻辑。只有搞懂了底层原理,才能在实际项目中正确使用并发工具,避免踩坑,快速定位和解决线上问题。

    赞(0)
    未经允许不得转载:171主机测评 » 别再只会用 synchronized!Java 并发编程全链路核心体系,从底层原理到生产实战全覆盖
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址