一、锁优化的核心基石:对象头与内存布局
在JVM中,synchronized的锁状态完全存储在Java对象的对象头中,理解对象头的结构是拆解锁膨胀机制的前提。64位JVM默认开启压缩指针时,对象头由Mark Word(标记字段)和Klass Pointer(类型指针)两部分组成,数组对象还会额外存储数组长度。其中Mark Word是锁实现的核心,固定占用8字节,存储了对象的哈希码、分代年龄、锁状态、线程ID等核心信息。
1.1 Mark Word的状态结构
Mark Word的存储结构会随着锁状态的变化动态复用,不同锁状态下的位分配如下表所示:
| 锁状态 | 高25位 | 31位分代年龄 | 1位偏向锁位 | 2位锁标记位 |
| 无锁 | 未使用 | 分代年龄 | 0 | 01 |
| 偏向锁 | 持有锁的线程ID | 分代年龄 | 1 | 01 |
| 轻量级锁 | 指向栈中LockRecord的指针 | - | - | 00 |
| 重量级锁 | 指向Monitor对象的指针 | - | - | 10 |
| GC标记 | 空 | - | - | 11 |
核心规则:锁标记位是判断锁状态的核心依据,其中无锁与偏向锁共用01标记位,通过偏向锁位做二次区分。
1.2 管程模型Monitor
重量级锁的底层实现依赖于Monitor管程对象,每个重量级锁都会关联一个独立的Monitor,其核心结构包含三个关键部分:
- _owner:指向当前持有锁的线程
- _EntryList:入口集,存储竞争锁失败被阻塞的线程
- _WaitSet:等待集,存储调用wait()方法进入等待状态的线程
Monitor的底层依赖操作系统的互斥量(mutex)实现,涉及用户态与内核态的切换,单次切换的开销约为数千纳秒,这也是重量级锁性能开销大的核心原因。
二、锁膨胀全链路流程与底层实现
锁膨胀的本质是JVM针对不同的线程竞争场景,自动选择开销最低的锁实现,当竞争加剧时,锁会从低开销状态逐步向高开销状态升级。整个流程遵循固定的升级路径,仅在GC安全点会发生锁降级。
2.1 锁膨胀全流程
2.2 各锁状态的底层实现与适用场景
2.2.1 无锁状态
无锁状态下,对象的Mark Word仅存储分代年龄、未使用的高位空间,偏向锁位为0,锁标记位为01。此时对象没有被任何线程加锁,是所有对象的初始状态。
2.2.2 偏向锁
偏向锁的核心设计理念是:无竞争场景下,消除同步操作的所有开销。其适用场景为单线程反复获取同一把锁,无多线程竞争。
- 核心原理:第一个获取锁的线程,会通过CAS将自己的线程ID写入对象的Mark Word,将对象标记为偏向当前线程。后续该线程再次获取锁时,仅需比对Mark Word中的线程ID,无需任何CAS操作,开销仅为几次内存读取,和无锁操作几乎无差别。
- 可重入实现:线程重入时,会在当前栈帧中创建一个无内容的Lock Record,作为重入计数的凭证,无需执行CAS操作,重入次数由栈中Lock Record的数量决定。
- 偏向撤销:当有第二个线程尝试竞争该锁时,会触发偏向撤销。偏向撤销必须在GC安全点执行,会暂停所有正在运行的线程,校验持有偏向锁的线程是否存活,若线程已退出则重置对象为无锁状态;若线程仍存活则升级为轻量级锁。
- 关键特性:JDK15及以上版本默认关闭偏向锁,需通过JVM参数
-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0手动开启。高并发场景下,偏向撤销的STW开销远大于偏向锁带来的收益,因此高并发系统不建议开启。
2.2.3 轻量级锁
轻量级锁的核心设计理念是:低竞争场景下,通过用户态CAS避免内核态的开销。其适用场景为多个线程交替获取锁,同一时刻无多线程同时竞争。
- 加锁流程:
- 线程在当前栈帧中创建名为Lock Record的空间,用于存储对象当前Mark Word的拷贝(官方称为Displaced Mark Word)。
- 线程通过CAS尝试将对象的Mark Word替换为指向当前栈中Lock Record的指针。
- 若CAS成功,当前线程获取轻量级锁,将锁标记位设置为00。
- 若CAS失败,说明存在竞争,线程会进入自适应自旋阶段,反复尝试CAS获取锁。
- 自适应自旋:自旋的次数不是固定值,JVM会根据同一个锁上一次的自旋成功率与持有锁线程的状态动态调整。若上一次自旋成功获取了锁,本次会增加自旋次数;若上一次自旋失败,本次会直接跳过自旋,升级为重量级锁。
- 可重入实现:线程每次重入都会在栈帧中创建一个新的Lock Record,存储Displaced Mark Word,重入次数由栈中Lock Record的数量决定。
- 锁释放:线程每次退出同步块都会释放一个Lock Record,当栈中所有Lock Record都被释放后,通过CAS将Displaced Mark Word还原到对象头中。若还原时存在竞争,锁会直接膨胀为重量级锁。
2.2.4 重量级锁
重量级锁是锁膨胀的最终阶段,核心设计理念是:高竞争场景下,通过内核态互斥量保证线程安全,避免CPU空转。其适用场景为多线程同时竞争同一把锁,竞争激烈。
- 核心原理:锁膨胀为重量级锁时,JVM会为对象创建关联的Monitor对象,将对象的Mark Word替换为指向Monitor的指针,锁标记位设置为10。竞争失败的线程会被加入Monitor的EntryList,进入内核态阻塞状态,直到持有锁的线程释放锁后被唤醒。
- 可重入实现:Monitor对象中维护了
_recursions字段,记录锁的重入次数,线程每次重入该字段加1,每次解锁减1,减至0时才会真正释放锁,唤醒EntryList中的等待线程。 - 内存语义:重量级锁的加锁与解锁操作具备完整的Happens-Before语义,解锁操作会将线程工作内存中的所有修改刷新到主内存,加锁操作会将工作内存失效,从主内存中读取最新数据,保证多线程之间的可见性、原子性与有序性。
2.3 锁降级机制
很多资料中提到锁膨胀是单向不可逆的,这个说法并不准确。JVM会在GC安全点,对无竞争的锁进行降级:
- 若重量级锁关联的Monitor没有线程持有,也没有线程在EntryList中等待,GC时会将其降级为无锁状态。
- 轻量级锁若没有线程竞争,GC时也会重置为无锁状态。 锁降级仅发生在GC过程中,不会在锁释放的过程中实时发生,因此常规业务场景中,我们仍可以认为锁的升级是单向的。
三、JVM内置锁优化补充机制
除了锁膨胀机制,JVM还提供了四项核心的锁优化技术,进一步降低同步操作的性能开销。
3.1 锁消除
锁消除是JIT编译器的优化技术,核心逻辑是:通过逃逸分析,判断某把锁的对象仅能被一个线程访问,不存在多线程竞争的可能,JIT会直接消除该锁的同步操作,避免无意义的加锁解锁开销。
package com.jam.demo;
import lombok.extern.slf4j.Slf4j;
/**
* 锁消除演示示例
* @author ken
*/
@Slf4j
public class LockEliminationDemo {
/**
* 锁消除场景演示
* StringBuffer的append方法是synchronized修饰的
* 但sb是局部变量,不会逃逸出当前方法,不存在多线程竞争
* JIT编译时会直接消除append方法的锁操作
*/
public String lockEliminationTest() {
StringBuffer sb = new StringBuffer();
sb.append("a");
sb.append("b");
sb.append("c");
return sb.toString();
}
public static void main(String[] args) {
LockEliminationDemo demo = new LockEliminationDemo();
// 触发JIT编译
for (int i = 0; i < 100000; i++) {
demo.lockEliminationTest();
}
}
}
锁消除的开启需要满足两个条件:JIT编译开启(默认开启)、逃逸分析开启(JVM参数-XX:+DoEscapeAnalysis,默认开启)。
3.2 锁粗化
锁粗化的核心逻辑是:当JIT检测到一系列连续的加锁解锁操作,都是针对同一个锁对象,会将锁的范围粗化到整个操作序列的外部,避免频繁的加锁解锁带来的性能开销。
package com.jam.demo;
import lombok.extern.slf4j.Slf4j;
/**
* 锁粗化演示示例
* @author ken
*/
@Slf4j
public class LockCoarseningDemo {
private final StringBuffer sb = new StringBuffer();
/**
* 锁粗化场景演示
* 循环内反复对同一个对象加锁解锁
* JIT会将锁粗化到循环外部,仅加锁解锁一次
*/
public void lockCoarseningTest() {
for (int i = 0; i < 100; i++) {
sb.append(i);
}
}
public static void main(String[] args) {
LockCoarseningDemo demo = new LockCoarseningDemo();
// 触发JIT编译
for (int i = 0; i < 100000; i++) {
demo.lockCoarseningTest();
}
}
}
锁粗化通过JVM参数-XX:+EliminateLocks开启,默认开启。
3.3 自适应自旋
自适应自旋在轻量级锁章节已经提及,核心是JVM会根据锁的历史竞争情况动态调整自旋次数,避免固定自旋次数带来的CPU空转或频繁内核态切换问题。该特性通过-XX:+UseAdaptiveSpinning开启,默认开启,不建议手动修改。
3.4 逃逸分析
逃逸分析是所有锁优化的基础,JVM通过逃逸分析判断对象的作用范围:
- 不逃逸:对象仅在当前方法内使用,不会被其他线程访问。
- 方法逃逸:对象被传递到其他方法中,但不会被外部线程访问。
- 线程逃逸:对象被外部线程访问,存在多线程竞争的可能。 只有不逃逸的对象,才会触发锁消除优化。逃逸分析通过
-XX:+DoEscapeAnalysis开启,默认开启。
四、锁膨胀可视化演示与代码实战
本节通过JOL(Java Object Layout)工具,可视化展示锁膨胀的完整过程。
4.1 环境依赖配置
首先在maven的pom.xml中添加所需依赖:
<?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 http://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.4</version>
<relativePath/>
</parent>
<groupId>com.jam.demo</groupId>
<artifactId>jvm-lock-demo</artifactId>
<version>1.0.0</version>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<mybatis-plus.version>3.5.6</mybatis-plus.version>
<fastjson2.version>2.0.52</fastjson2.version>
<guava.version>33.1.0-jre</guava.version>
<jol.version>0.17</jol.version>
<jmh.version>1.37</jmh.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.5.0</version>
</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>1.18.32</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.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>${jol.version}</version>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
<version>${jmh.version}</version>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<version>${jmh.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<source>17</source>
<target>17</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
4.2 锁膨胀全流程可视化代码
package com.jam.demo;
import org.openjdk.jol.info.ClassLayout;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.CountDownLatch;
/**
* 锁膨胀全流程可视化演示
* @author ken
*/
@Slf4j
public class LockInflationDemo {
private static final Object lockObj = new Object();
public static void main(String[] args) throws InterruptedException {
log.info("1. 无锁状态,对象头信息:");
printObjectHead();
Thread.sleep(1000);
// 单线程加锁,演示轻量级锁(JDK17默认关闭偏向锁)
synchronized (lockObj) {
log.info("2. 单线程持有锁,轻量级锁状态,对象头信息:");
printObjectHead();
}
log.info("3. 轻量级锁释放后,对象头信息:");
printObjectHead();
Thread.sleep(1000);
// 多线程竞争,演示膨胀为重量级锁
CountDownLatch latch = new CountDownLatch(2);
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 100; i++) {
synchronized (lockObj) {
if (i == 50) {
log.info("4. 多线程竞争下,重量级锁状态,对象头信息:");
printObjectHead();
}
}
}
latch.countDown();
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 100; i++) {
synchronized (lockObj) {
}
}
latch.countDown();
});
thread1.start();
thread2.start();
latch.await();
Thread.sleep(1000);
log.info("5. 重量级锁释放后,对象头信息:");
printObjectHead();
}
/**
* 打印对象头信息
*/
private static void printObjectHead() {
System.out.println(ClassLayout.parseInstance(lockObj).toPrintable());
}
}
运行说明:
- 若需演示偏向锁,需添加JVM启动参数:
-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0 - 控制台输出的对象头中,前8字节为Mark Word,可通过锁标记位判断当前锁状态。
4.3 高并发场景锁性能基准测试
通过JMH官方基准测试工具,对比不同锁实现的吞吐量性能,代码如下:
package com.jam.demo;
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.LongAdder;
import java.util.concurrent.locks.ReentrantLock;
/**
* 高并发锁性能基准测试
* @author ken
*/
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Warmup(iterations = 3, time = 1, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Fork(1)
@Threads(10)
@State(Scope.Benchmark)
public class LockPerformanceBenchmark {
private int syncCount = 0;
private final Object lockObj = new Object();
private final AtomicInteger atomicCount = new AtomicInteger(0);
private final LongAdder adderCount = new LongAdder();
private final ReentrantLock reentrantLock = new ReentrantLock();
private int lockCount = 0;
@Benchmark
public void syncLockTest() {
synchronized (lockObj) {
syncCount++;
}
}
@Benchmark
public void atomicIntegerTest() {
atomicCount.incrementAndGet();
}
@Benchmark
public void longAdderTest() {
adderCount.increment();
}
@Benchmark
public void reentrantLockTest() {
reentrantLock.lock();
try {
lockCount++;
} finally {
reentrantLock.unlock();
}
}
public static void main(String[] args) throws RunnerException {
Options options = new OptionsBuilder()
.include(LockPerformanceBenchmark.class.getSimpleName())
.build();
new Runner(options).run();
}
}
测试结论:
- 无竞争场景下:偏向锁性能最优,与无锁操作几乎无差别,其次是轻量级锁、AtomicInteger、ReentrantLock。
- 低竞争场景下:轻量级锁性能优于重量级锁,AtomicInteger性能优于synchronized与ReentrantLock。
- 高竞争场景下:LongAdder性能最优,其次是AtomicInteger,ReentrantLock与重量级锁synchronized性能接近。
五、高并发业务场景锁调优实战
本节以电商核心的库存扣减场景为例,结合MyBatisPlus与MySQL8.0,演示高并发场景下的锁调优方案。
5.1 数据库表结构
CREATE TABLE `t_inventory` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`sku_id` bigint NOT NULL COMMENT '商品SKU 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_sku_id` (`sku_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='商品库存表';
5.2 实体类定义
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_inventory")
@Schema(description = "商品库存实体")
public class Inventory {
@TableId(type = IdType.AUTO)
@Schema(description = "主键ID", example = "1")
private Long id;
@Schema(description = "商品SKU ID", example = "1001")
private Long skuId;
@Schema(description = "库存数量", example = "1000")
private Integer stockNum;
@Version
@Schema(description = "乐观锁版本号", example = "0")
private Integer version;
@TableField(fill = FieldFill.INSERT)
@Schema(description = "创建时间")
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
@Schema(description = "更新时间")
private LocalDateTime updateTime;
}
5.3 Mapper层定义
package com.jam.demo.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jam.demo.entity.Inventory;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Update;
/**
* 库存Mapper接口
* @author ken
*/
public interface InventoryMapper extends BaseMapper<Inventory> {
/**
* 悲观锁扣减库存
* @param skuId 商品SKU ID
* @param num 扣减数量
* @return 影响行数
*/
@Update("UPDATE t_inventory SET stock_num = stock_num - #{num} WHERE sku_id = #{skuId} AND stock_num >= #{num}")
int deductStockByPessimisticLock(@Param("skuId") Long skuId, @Param("num") Integer num);
}
5.4 Service层实现
package com.jam.demo.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.jam.demo.entity.Inventory;
import com.jam.demo.mapper.InventoryMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.util.ObjectUtils;
/**
* 库存服务实现类
* @author ken
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class InventoryService {
private final InventoryMapper inventoryMapper;
private final PlatformTransactionManager transactionManager;
/**
* 乐观锁扣减库存
* @param skuId 商品SKU ID
* @param deductNum 扣减数量
* @return 扣减结果
*/
public boolean deductStockByOptimisticLock(Long skuId, Integer deductNum) {
if (ObjectUtils.isEmpty(skuId) || ObjectUtils.isEmpty(deductNum) || deductNum <= 0) {
log.error("库存扣减参数异常,skuId:{},deductNum:{}", skuId, deductNum);
return false;
}
// 编程式事务定义
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
def.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
TransactionStatus status = transactionManager.getTransaction(def);
try {
// 查询库存信息
Inventory inventory = inventoryMapper.selectOne(
new LambdaQueryWrapper<Inventory>()
.eq(Inventory::getSkuId, skuId)
);
if (ObjectUtils.isEmpty(inventory)) {
log.error("商品库存不存在,skuId:{}", skuId);
transactionManager.rollback(status);
return false;
}
if (inventory.getStockNum() < deductNum) {
log.warn("商品库存不足,skuId:{},currentStock:{},deductNum:{}", skuId, inventory.getStockNum(), deductNum);
transactionManager.rollback(status);
return false;
}
// 乐观锁更新,版本号自动递增
LambdaUpdateWrapper<Inventory> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(Inventory::getId, inventory.getId())
.eq(Inventory::getVersion, inventory.getVersion())
.setSql("stock_num = stock_num - " + deductNum);
int updateRows = inventoryMapper.update(null, updateWrapper);
if (updateRows > 0) {
transactionManager.commit(status);
log.info("乐观锁库存扣减成功,skuId:{},deductNum:{}", skuId, deductNum);
return true;
} else {
transactionManager.rollback(status);
log.warn("乐观锁更新失败,存在并发竞争,skuId:{}", skuId);
return false;
}
} catch (Exception e) {
transactionManager.rollback(status);
log.error("库存扣减异常,skuId:{}", skuId, e);
return false;
}
}
/**
* 悲观锁扣减库存
* @param skuId 商品SKU ID
* @param deductNum 扣减数量
* @return 扣减结果
*/
public boolean deductStockByPessimisticLock(Long skuId, Integer deductNum) {
if (ObjectUtils.isEmpty(skuId) || ObjectUtils.isEmpty(deductNum) || deductNum <= 0) {
log.error("库存扣减参数异常,skuId:{},deductNum:{}", skuId, deductNum);
return false;
}
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
def.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
TransactionStatus status = transactionManager.getTransaction(def);
try {
int updateRows = inventoryMapper.deductStockByPessimisticLock(skuId, deductNum);
if (updateRows > 0) {
transactionManager.commit(status);
log.info("悲观锁库存扣减成功,skuId:{},deductNum:{}", skuId, deductNum);
return true;
} else {
transactionManager.rollback(status);
log.warn("库存不足,扣减失败,skuId:{},deductNum:{}", skuId, deductNum);
return false;
}
} catch (Exception e) {
transactionManager.rollback(status);
log.error("库存扣减异常,skuId:{}", skuId, e);
return false;
}
}
}
5.5 Controller层实现
package com.jam.demo.controller;
import com.jam.demo.service.InventoryService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* 库存操作接口
* @author ken
*/
@RestController
@RequestMapping("/inventory")
@RequiredArgsConstructor
@Tag(name = "库存管理", description = "商品库存扣减相关接口")
public class InventoryController {
private final InventoryService inventoryService;
@PostMapping("/deduct/optimistic")
@Operation(summary = "乐观锁扣减库存", description = "高并发场景推荐使用,无锁阻塞开销")
public boolean deductByOptimisticLock(
@Parameter(description = "商品SKU ID", required = true) @RequestParam Long skuId,
@Parameter(description = "扣减数量", required = true) @RequestParam Integer deductNum) {
return inventoryService.deductStockByOptimisticLock(skuId, deductNum);
}
@PostMapping("/deduct/pessimistic")
@Operation(summary = "悲观锁扣减库存", description = "并发量较低、数据一致性要求极高的场景使用")
public boolean deductByPessimisticLock(
@Parameter(description = "商品SKU ID", required = true) @RequestParam Long skuId,
@Parameter(description = "扣减数量", required = true) @RequestParam Integer deductNum) {
return inventoryService.deductStockByPessimisticLock(skuId, deductNum);
}
}
5.6 场景调优方案
- 高并发秒杀场景:优先使用乐观锁方案,避免JVM锁与数据库行锁带来的阻塞开销,同时结合预扣库存、库存分段、消息队列削峰等方案,进一步降低锁竞争。
- 低并发高一致性场景:可使用悲观锁方案,保证数据强一致性,同时缩小锁的范围,避免长事务持有锁。
- JVM锁调优:避免在分布式场景下使用JVM锁,分布式场景优先使用分布式锁(如Redis分布式锁、Zookeeper分布式锁);单机场景优先缩小锁的粒度与持有时间,降低锁竞争的概率。
六、易混淆技术点明确区分
6.1 轻量级锁 ≠ 自旋锁
轻量级锁是JVM定义的一种锁状态,核心是通过用户态CAS实现同步;自旋是锁竞争失败后的一种重试机制,不仅轻量级锁会使用自旋,重量级锁在进入内核态阻塞之前也会尝试自旋。自旋是一种优化手段,不是锁的状态,二者不能划等号。
6.2 偏向锁撤销 ≠ 锁释放
偏向锁撤销是指多线程竞争时,取消对象的偏向状态,将锁升级为轻量级锁的过程,该过程需要在GC安全点执行,会触发STW;锁释放是线程执行完同步块,主动释放锁的过程,不会触发STW。二者是完全不同的操作,不能混淆。
6.3 用户态锁 vs 内核态锁
- 用户态锁:偏向锁、轻量级锁的实现完全在用户态完成,不需要操作系统内核介入,不会发生用户态与内核态的切换,开销极低。
- 内核态锁:重量级锁依赖操作系统的互斥量实现,线程的阻塞与唤醒都需要内核介入,会发生用户态与内核态的切换,开销极大。
6.4 synchronized vs ReentrantLock
| 特性 | synchronized | ReentrantLock |
| 实现层面 | JVM层面实现,由字节码指令控制 | JDK层面实现,基于AQS框架 |
| 锁优化 | 支持锁膨胀、锁消除、锁粗化等全链路优化 | 仅支持公平锁/非公平锁、可中断等特性 |
| 可重入性 | 支持,底层通过Mark Word/Monitor实现 | 支持,底层通过AQS的state字段实现 |
| 公平锁 | 仅支持非公平锁 | 支持公平锁与非公平锁,可通过构造方法指定 |
| 高级特性 | 无 | 支持可中断获取锁、超时获取锁、多条件变量 |
| 释放方式 | 自动释放,异常时也会释放 | 必须手动在finally块中释放,否则会造成死锁 |
七、生产环境锁调优最佳实践
7.1 锁竞争问题排查工具
- jstack:JDK自带的线程堆栈分析工具,可查看线程的锁持有状态、锁等待状态,快速定位死锁与锁竞争问题。
- Arthas:阿里开源的Java诊断工具,可通过
vmtool命令查看对象头的锁状态,通过thread命令查看线程的锁竞争情况。 - JProfiler/AsyncProfiler:性能分析工具,可采集锁的持有时间、竞争次数,定位性能瓶颈。
7.2 核心调优原则
- 优先无锁设计:最优的锁优化永远是消除锁。通过ThreadLocal、不可变对象、无锁并发工具类(如LongAdder、ConcurrentHashMap)等方案,避免锁竞争。
- 缩小锁粒度:仅对需要同步的代码块加锁,避免对整个方法加锁,减少锁的持有时间。
- 降低锁的持有时间:将耗时操作(如IO操作、网络调用)移出同步块,避免持有锁的同时执行耗时操作,加剧锁竞争。
- 避免锁嵌套:严格控制加锁顺序,所有线程按照相同的顺序加锁,避免死锁问题。
- 合理选择锁类型:
- 单线程无竞争场景:开启偏向锁,获得最优性能。
- 多线程交替加锁场景:使用轻量级锁,避免内核态开销。
- 高并发竞争场景:优先使用乐观锁、JUC并发工具类,避免重量级锁。
- JVM参数调优:
- 高并发场景:保持JDK17默认配置,关闭偏向锁,避免偏向撤销的STW开销。
- 低延迟系统:通过
-XX:PreBlockSpin调整自旋次数,减少内核态切换的概率(不建议手动调整,优先使用自适应自旋)。 - 开启逃逸分析与锁消除:保持默认开启状态,JIT会自动优化无意义的锁操作。
7.3 分布式场景注意事项
JVM锁仅能保证单机内的线程安全,分布式场景下必须使用分布式锁,避免出现数据不一致问题。分布式锁优先选择Redis Redlock、Zookeeper分布式锁等成熟方案,同时保证锁的可重入性、超时释放、死锁预防等特性。
八、总结
JVM的锁膨胀机制,本质上是JVM在性能与线程安全之间做的动态平衡,针对不同的竞争场景,提供了不同开销的锁实现。从无锁到偏向锁、轻量级锁、重量级锁,每一次锁膨胀都是JVM针对竞争加剧做出的自适应调整。
理解锁膨胀的底层逻辑,不仅能打破对synchronized的刻板印象,更能在开发中写出更高效的同步代码,在生产环境出现锁竞争问题时,快速定位根因,给出针对性的调优方案。在高并发系统设计中,我们需要始终记住:锁优化的核心不是把锁的性能调到极致,而是通过合理的架构设计,从根源上消除锁竞争。