Java 锁机制全面详解
本文系统梳理 Java 中各类锁的核心概念、适用场景及代码实现,助你彻底搞懂并发编程中的"锁"事。
目录
- [一、悲观锁 vs 乐观锁](#一、悲观锁 vs 乐观锁)
- [二、公平锁 vs 非公平锁](#二、公平锁 vs 非公平锁)
- [三、可重入锁 vs 不可重入锁](#三、可重入锁 vs 不可重入锁)
- [四、自旋锁 vs 阻塞锁](#四、自旋锁 vs 阻塞锁)
- [五、偏向锁 / 轻量级锁 / 重量级锁](#五、偏向锁 / 轻量级锁 / 重量级锁)
- 六、各类锁速查对照表
一、悲观锁 vs 乐观锁
1.1 核心思想对比
| 维度 |
悲观锁 |
乐观锁 |
| 核心假设 |
并发冲突很严重,每次操作都会冲突 |
并发冲突不严重,多数操作不会冲突 |
| 加锁方式 |
操作数据前先加锁 |
不加锁,通过版本号/CAS 检测冲突 |
| 典型实现 |
synchronized、ReentrantLock、数据库 SELECT FOR UPDATE |
数据库 version 字段、AtomicInteger、CAS 操作 |
| 适用场景 |
写多读少、冲突频繁 |
读多写少、冲突较少 |
| 性能特点 |
加锁开销大,但冲突时安全 |
无锁开销小,但冲突频繁时重试成本高 |
1.2 悲观锁代码示例
(1)synchronized 实现悲观锁
/**
* 使用 synchronized 实现悲观锁
* 每次只能有一个线程进入同步方法,其他线程阻塞等待
*/
public class PessimisticLockDemo {
private int count = 0;
// synchronized 修饰实例方法 → 锁住当前对象
public synchronized void increment() {
count++;
}
// synchronized 修饰代码块 → 锁住指定对象
public void incrementBlock() {
synchronized (this) {
count++;
}
}
// 锁住 class 对象 → 类级别的锁
public static synchronized void staticMethod() {
System.out.println("类级悲观锁");
}
public int getCount() {
return count;
}
// ===== 测试 =====
public static void main(String[] args) throws InterruptedException {
PessimisticLockDemo demo = new PessimisticLockDemo();
int threadCount = 10;
Thread[] threads = new Thread[threadCount];
for (int i = 0; i < threadCount; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < 1000; j++) {
demo.increment();
}
});
threads[i].start();
}
for (Thread t : threads) {
t.join();
}
System.out.println("最终结果: " + demo.getCount()); // 10000
}
}
(2)ReentrantLock 实现悲观锁
import java.util.concurrent.locks.ReentrantLock;
/**
* 使用 ReentrantLock 实现悲观锁
* 比 synchronized 更灵活:可中断、可超时、可公平
*/
public class ReentrantLockPessimisticDemo {
private final ReentrantLock lock = new ReentrantLock();
private int count = 0;
public void increment() {
lock.lock(); // 获取锁(悲观:假设一定会冲突)
try {
count++;
} finally {
lock.unlock(); // 必须在 finally 中释放
}
}
// 可中断的加锁方式
public void incrementInterruptibly() throws InterruptedException {
lock.lockInterruptibly(); // 允许被中断
try {
count++;
} finally {
lock.unlock();
}
}
// 带超时的加锁方式
public boolean incrementWithTimeout() throws InterruptedException {
// 最多等 3 秒
if (lock.tryLock(3, java.util.concurrent.TimeUnit.SECONDS)) {
try {
count++;
return true;
} finally {
lock.unlock();
}
}
return false; // 超时未获取到锁
}
public int getCount() {
return count;
}
}
(3)数据库层面的悲观锁
-- MySQL:查询时加排他锁(悲观锁)
-- 事务未提交前,其他事务无法修改这条数据
START TRANSACTION;
SELECT * FROM account WHERE id = 1 FOR UPDATE;
UPDATE account SET balance = balance - 100 WHERE id = 1;
COMMIT;
1.3 乐观锁代码示例
(1)版本号机制(数据库乐观锁)
-- 建表时增加 version 字段
CREATE TABLE account (
id BIGINT PRIMARY KEY,
balance DECIMAL(10, 2),
version INT DEFAULT 0
);
-- 更新时校验版本号(乐观:假设不会冲突)
UPDATE account
SET balance = balance - 100,
version = version + 1
WHERE id = 1
AND version = 0; -- 只有版本号匹配才更新成功
-- 如果返回 affected rows = 0,说明数据已被其他事务修改,需要重试
/**
* 数据库乐观锁 Java 实现
*/
public class OptimisticLockDBDemo {
@Autowired
private JdbcTemplate jdbcTemplate;
public boolean transfer(Long accountId, BigDecimal amount) {
int maxRetry = 3;
for (int i = 0; i < maxRetry; i++) {
// 1. 查询当前数据和版本号
Map<String, Object> row = jdbcTemplate.queryForMap(
"SELECT balance, version FROM account WHERE id = ?", accountId);
BigDecimal balance = (BigDecimal) row.get("balance");
int version = (int) row.get("version");
// 2. 计算新余额
BigDecimal newBalance = balance.subtract(amount);
if (newBalance.compareTo(BigDecimal.ZERO) < 0) {
return false; // 余额不足
}
// 3. 带版本号更新(CAS 思想)
int updated = jdbcTemplate.update(
"UPDATE account SET balance = ?, version = version + 1 " +
"WHERE id = ? AND version = ?",
newBalance, accountId, version);
if (updated > 0) {
return true; // 更新成功
}
// updated == 0 说明版本号已被其他线程修改,重试
System.out.println("版本冲突,重试第 " + (i + 1) + " 次");
}
return false; // 重试多次仍失败
}
}
(2)CAS 机制(AtomicInteger 乐观锁)
import java.util.concurrent.atomic.AtomicInteger;
/**
* 使用 CAS(Compare-And-Swap)实现乐观锁
* 底层调用 Unsafe.compareAndSwapInt,无锁并发
*/
public class CASOptimisticDemo {
private final AtomicInteger count = new AtomicInteger(0);
// CAS 自旋重试
public void increment() {
while (true) {
int oldValue = count.get();
int newValue = oldValue + 1;
// 乐观:期望值是 oldValue,如果是则更新为 newValue
if (count.compareAndSet(oldValue, newValue)) {
break; // CAS 成功,退出
}
// CAS 失败 → 说明有别的线程改了,重试
}
}
// 更简洁的方式(内部已做 CAS)
public void incrementSimple() {
count.incrementAndGet();
}
public int getCount() {
return count.get();
}
// ===== 测试 =====
public static void main(String[] args) throws InterruptedException {
CASOptimisticDemo demo = new CASOptimisticDemo();
int threadCount = 10;
Thread[] threads = new Thread[threadCount];
for (int i = 0; i < threadCount; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < 1000; j++) {
demo.increment();
}
});
threads[i].start();
}
for (Thread t : threads) {
t.join();
}
System.out.println("最终结果: " + demo.getCount()); // 10000
}
}
(3)AtomicReference 实现自定义对象的乐观锁 (可解决ABA问题)
import java.util.concurrent.atomic.AtomicReference;
class Account {
private final String userId;
private final BigDecimal balance;
public Account(String userId, BigDecimal balance) {
this.userId = userId;
this.balance = balance;
}
// getters...
}
/**
* 使用 AtomicReference + CAS 实现对象级别的乐观锁
*/
public class AtomicReferenceDemo {
private final AtomicReference<Account> accountRef =
new AtomicReference<>(new Account("user1", new BigDecimal("1000")));
public boolean withdraw(BigDecimal amount) {
while (true) {
Account current = accountRef.get();
BigDecimal newBalance = current.getBalance().subtract(amount);
if (newBalance.compareTo(BigDecimal.ZERO) < 0) {
return false; // 余额不足
}
Account updated = new Account(current.getUserId(), newBalance);
// CAS:只有当前引用没被其他线程修改才更新
if (accountRef.compareAndSet(current, updated)) {
return true;
}
// 失败则重试
}
}
}
二、公平锁 vs 非公平锁
2.1 核心思想对比
| 维度 |
公平锁 |
非公平锁 |
| 获取顺序 |
严格按照 FIFO 队列顺序,先到先得 |
允许新来的线程"插队"先抢锁 |
| 吞吐量 |
较低(线程切换多) |
较高(减少上下文切换) |
| 饥饿问题 |
无饥饿 |
可能导致队列尾部线程饥饿 |
| 典型实现 |
new ReentrantLock(true) |
synchronized、new ReentrantLock(false) |
2.2 公平锁代码示例
import java.util.concurrent.locks.ReentrantLock;
/**
* 公平锁:严格按照线程请求顺序获取锁
* 先来先得,绝不插队
*/
public class FairLockDemo {
// true → 公平锁
private final ReentrantLock fairLock = new ReentrantLock(true);
public void doWork(String taskName) {
fairLock.lock();
try {
System.out.println(Thread.currentThread().getName()
+ " 获取到公平锁,执行任务: " + taskName);
Thread.sleep(500); // 模拟业务耗时
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
fairLock.unlock();
}
}
public static void main(String[] args) {
Test demo = new Test();
// 按顺序启动 5 个线程
for (int i = 1; i <= 3; i++) {
final int idx = i;
new Thread(() -> {
for (int j = 0; j < 5; j++) {
demo.doWork("Task-" + idx + "-" + j);
}
}, "Thread-" + idx).start();
}
// 输出顺序:每轮循环线程顺序相等
}
}
Thread-3 获取到公平锁,执行任务: Task-3-0
Thread-2 获取到公平锁,执行任务: Task-2-0
Thread-1 获取到公平锁,执行任务: Task-1-0
Thread-3 获取到公平锁,执行任务: Task-3-1
Thread-2 获取到公平锁,执行任务: Task-2-1
Thread-1 获取到公平锁,执行任务: Task-1-1
Thread-3 获取到公平锁,执行任务: Task-3-2
Thread-2 获取到公平锁,执行任务: Task-2-2
Thread-1 获取到公平锁,执行任务: Task-1-2
Thread-3 获取到公平锁,执行任务: Task-3-3
Thread-2 获取到公平锁,执行任务: Task-2-3
Thread-1 获取到公平锁,执行任务: Task-1-3
Thread-3 获取到公平锁,执行任务: Task-3-4
Thread-2 获取到公平锁,执行任务: Task-2-4
Thread-1 获取到公平锁,执行任务: Task-1-4
2.3 非公平锁代码示例
import java.util.concurrent.locks.ReentrantLock;
/**
* 非公平锁:新来的线程可以先尝试抢锁
* 抢不到再乖乖排队
*/
public class NonFairLockDemo {
// false → 非公平锁(默认)
private final ReentrantLock nonFairLock = new ReentrantLock(false);
public void doWork(String taskName) {
nonFairLock.lock();
try {
System.out.println(Thread.currentThread().getName()
+ " 获取到非公平锁,执行: " + taskName);
Thread.sleep(500);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
nonFairLock.unlock();
}
}
public static void main(String[] args) {
NonFairLockDemo demo = new NonFairLockDemo();
// 非公平场景下,后到的线程可能先拿到锁
for (int i = 1; i <= 5; i++) {
final int idx = i;
new Thread(() -> demo.doWork("Task-" + idx), "Thread-" + idx).start();
}
// 输出顺序不确定,可能出现"插队"现象
}
}
2.4 synchronized 的非公平性演示
/**
* synchronized 是非公平锁
* 无法保证线程获取锁的顺序
*/
public class SynchronizedNonFairDemo {
public synchronized void method() {
System.out.println(Thread.currentThread().getName() + " 获取锁");
try { Thread.sleep(1000); } catch (InterruptedException ignored) {}
}
public static void main(String[] args) {
SynchronizedNonFairDemo demo = new SynchronizedNonFairDemo();
for (int i = 0; i < 5; i++) {
new Thread(demo::method, "T" + i).start();
}
// 执行顺序 ≠ 启动顺序(非公平)
}
}
三、可重入锁 vs 不可重入锁
3.1 核心思想对比
| 维度 |
可重入锁 |
不可重入锁 |
| 同一线程重复获取 |
✅ 可以,计数 +1 |
❌ 不可以,会死锁 |
| 典型实现 |
synchronized、ReentrantLock |
自定义简易锁 |
| 实现原理 |
记录持有线程 + 计数 |
仅记录是否被占用 |
3.2 可重入锁代码示例
(1)synchronized 可重入
/**
* synchronized 天然支持可重入
* 同一线程可以多次进入同步方法而不会被自己锁住
*/
public class SynchronizedReentrantDemo {
public synchronized void methodA() {
System.out.println("methodA 执行中...");
methodB(); // 同一线程再次获取锁 → 可重入
}
public synchronized void methodB() {
System.out.println("methodB 执行中(可重入)...");
}
public static void main(String[] args) {
new SynchronizedReentrantDemo().methodA();
// 输出:
// methodA 执行中...
// methodB 执行中(可重入)...
}
}
(2)ReentrantLock 可重入
import java.util.concurrent.locks.ReentrantLock;
/**
* ReentrantLock 可重入演示
* 同一线程多次 lock(),需要对应次数 unlock()
*/
public class ReentrantLockDemo {
private final ReentrantLock lock = new ReentrantLock();
public void outerMethod() {
lock.lock();
try {
System.out.println("外层方法获取锁,锁计数: " + lock.getHoldCount());
innerMethod(); // 递归调用,再次获取同一把锁
} finally {
lock.unlock();
}
}
public void innerMethod() {
lock.lock();
try {
System.out.println("内层方法获取锁,锁计数: " + lock.getHoldCount());
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
new ReentrantLockDemo().outerMethod();
// 输出:
// 外层方法获取锁,锁计数: 1
// 内层方法获取锁,锁计数: 2
}
}
3.3 不可重入锁代码示例
/**
* 自定义不可重入锁
* 同一线程第二次获取锁时会阻塞(死锁)
*/
public class NonReentrantLock {
private boolean isLocked = false;
private Thread lockedBy = null;
public synchronized void lock() throws InterruptedException {
// 如果锁已被占用,且不是当前线程持有 → 等待
while (isLocked && lockedBy != Thread.currentThread()) {
wait();
}
isLocked = true;
lockedBy = Thread.currentThread();
}
public synchronized void unlock() {
if (lockedBy == Thread.currentThread()) {
isLocked = false;
lockedBy = null;
notify();
}
}
// ===== 测试不可重入的后果 =====
public static void main(String[] args) {
NonReentrantLock lock = new NonReentrantLock();
new Thread(() -> {
try {
lock.lock();
System.out.println("第一次获取锁成功");
// 同一线程再次获取 → 不可重入,此处会死锁!
lock.lock(); // ⚠️ 这里会永远阻塞
System.out.println("第二次获取锁成功(不可重入锁永远到不了这里)");
lock.unlock();
lock.unlock();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}).start();
}
}
四、自旋锁 vs 阻塞锁
4.1 核心思想对比
| 维度 |
自旋锁 |
阻塞锁 |
| 获取锁失败时的行为 |
循环重试(忙等) |
线程挂起,等待唤醒 |
| CPU 消耗 |
高(占用 CPU 空转) |
低(让出 CPU) |
| 响应延迟 |
极低(不用上下文切换) |
较高(需内核态切换) |
| 适用场景 |
锁持有时间极短(纳秒~微秒级) |
锁持有时间较长 |
| 典型实现 |
CAS 自旋、AtomicInteger |
synchronized、ReentrantLock |
4.2 自旋锁代码示例
import java.util.concurrent.atomic.AtomicReference;
/**
* 自定义自旋锁
* 获取锁失败时不断循环尝试,不放弃 CPU
*/
public class SpinLock {
private final AtomicReference<Thread> owner = new AtomicReference<>();
/**
* 自旋获取锁
* 不断 CAS 尝试,直到成功
*/
public void lock() {
Thread current = Thread.currentThread();
// 自旋:不断尝试将 owner 从 null 改为当前线程
while (!owner.compareAndSet(null, current)) {
// 可选:让出 CPU,减少空转消耗
Thread.yield();
}
}
/**
* 释放锁
*/
public void unlock() {
Thread current = Thread.currentThread();
owner.compareAndSet(current, null);
}
// ===== 测试 =====
public static void main(String[] args) throws InterruptedException {
SpinLock spinLock = new SpinLock();
int[] counter = {0};
Runnable task = () -> {
for (int i = 0; i < 10000; i++) {
spinLock.lock();
try {
counter[0]++;
} finally {
spinLock.unlock();
}
}
};
Thread t1 = new Thread(task);
Thread t2 = new Thread(task);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("最终结果: " + counter[0]); // 20000
}
}
4.3 自适应自旋锁(优化版)
import java.util.concurrent.atomic.AtomicReference;
/**
* 带最大自旋次数的自适应自旋锁
* 防止长时间空转浪费 CPU
*/
public class AdaptiveSpinLock {
private final AtomicReference<Thread> owner = new AtomicReference<>();
private static final int MAX_SPIN = 1000; // 最大自旋次数
public boolean tryLock() {
Thread current = Thread.currentThread();
return owner.compareAndSet(null, current);
}
public void lock() {
Thread current = Thread.currentThread();
int spinCount = 0;
while (!owner.compareAndSet(null, current)) {
spinCount++;
if (spinCount >= MAX_SPIN) {
// 自旋次数过多,退化为阻塞
try {
Thread.sleep(1); // 让出 CPU
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
spinCount = 0; // 重置计数
} else {
Thread.yield(); // 轻量级让出
}
}
}
public void unlock() {
Thread current = Thread.currentThread();
owner.compareAndSet(current, null);
}
}
4.4 阻塞锁代码示例
import java.util.concurrent.locks.ReentrantLock;
/**
* 阻塞锁:获取不到锁时线程挂起
* 由 JVM/OS 管理线程状态切换
*/
public class BlockingLockDemo {
private final ReentrantLock lock = new ReentrantLock();
private int sharedData = 0;
public void longTask() {
lock.lock(); // 获取不到锁 → 线程挂起(BLOCKED / WAITING 状态)
try {
System.out.println(Thread.currentThread().getName() + " 获取锁");
sharedData++;
Thread.sleep(2000); // 模拟耗时操作(锁持有时间长)
System.out.println(Thread.currentThread().getName() + " 完成,数据=" + sharedData);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
BlockingLockDemo demo = new BlockingLockDemo();
for (int i = 0; i < 3; i++) {
new Thread(demo::longTask, "Worker-" + i).start();
}
// 线程会依次执行,未获取到锁的线程进入 BLOCKED 状态
// 通过 jstack 可以看到线程状态为 BLOCKED
}
}
4.5 自旋锁 vs 阻塞锁 选择建议
/**
* 实战建议:根据锁持有时间选择策略
*/
public class LockStrategyGuide {
// ✅ 适合自旋锁:操作极快(几行代码)
public void shortOperation() {
// 例如:计数器递增、状态标志翻转
// 耗时:纳秒~微秒级 → 自旋代价 < 上下文切换代价
}
// ✅ 适合阻塞锁:操作耗时较长
public void longOperation() {
// 例如:IO 操作、网络调用、复杂计算
// 耗时:毫秒级以上 → 上下文切换代价 < 自旋代价
}
}
五、偏向锁 / 轻量级锁 / 重量级锁
这三种锁是 synchronized 在 JVM 层面的锁升级机制,目的是在无竞争→低竞争→高竞争的不同场景下,自动选择最优的加锁策略。
5.1 锁升级流程
无锁 ──(第一个线程进入)──→ 偏向锁 ──(第二个线程竞争)──→ 轻量级锁 ──(竞争激烈)──→ 重量级锁
5.2 三种锁对比
| 锁状态 |
触发条件 |
实现机制 |
开销 |
| 偏向锁 |
只有一个线程访问同步块 |
在对象头 Mark Word 记录线程 ID |
几乎为零 |
| 轻量级锁 |
多个线程交替执行,无真正竞争 |
CAS 自旋 + 栈帧锁记录 |
较小 |
| 重量级锁 |
多线程同时竞争 |
操作系统互斥量(mutex)+ 线程阻塞/唤醒 |
较大 |
5.3 代码示例与验证
/**
* synchronized 锁升级演示
*
* 注意:需要关闭偏向锁延迟才能观察偏向锁
* JVM 参数:-XX:BiasedLockingStartupDelay=0
* -XX:+UseBiasedLocking
* -XX:+PrintFlagsFinal
*
* 打印锁状态工具:-XX:+PrintBiasedLockingStatistics
*/
public class LockUpgradeDemo {
private static final Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
// ===== 阶段1:偏向锁 =====
// 只有一个线程访问 → JVM 使用偏向锁
synchronized (lock) {
System.out.println("阶段1:单线程获取锁(偏向锁)");
printLockState(lock);
}
// ===== 阶段2:轻量级锁 =====
// 多个线程交替执行,无真正竞争
Thread t1 = new Thread(() -> {
synchronized (lock) {
System.out.println("阶段2:线程1交替执行(轻量级锁)");
printLockState(lock);
}
});
t1.start();
t1.join();
Thread t2 = new Thread(() -> {
synchronized (lock) {
System.out.println("阶段2:线程2交替执行(轻量级锁)");
printLockState(lock);
}
});
t2.start();
t2.join();
// ===== 阶段3:重量级锁 =====
// 多线程同时竞争 → 升级为重量级锁
int threadCount = 4;
Thread[] threads = new Thread[threadCount];
for (int i = 0; i < threadCount; i++) {
threads[i] = new Thread(() -> {
synchronized (lock) {
System.out.println(Thread.currentThread().getName()
+ " 竞争锁(重量级锁)");
try { Thread.sleep(1000); } catch (InterruptedException ignored) {}
}
}, "CompeteThread-" + i);
}
for (Thread t : threads) {
t.start();
}
for (Thread t : threads) {
t.join();
}
}
/**
* 打印对象锁状态(通过反射读取 Mark Word)
* 实际项目中可使用 JOL (Java Object Layout) 工具
*/
private static void printLockState(Object obj) {
// 使用 org.openjdk.jol:jol-core 工具打印
// System.out.println(ClassLayout.parseInstance(obj).toPrintable());
// 输出示例:
// 偏向锁: mark word = 0x... | 101 (末尾 101 = biased)
// 轻量级: mark word = 0x... | 00 (末尾 00 = lightweight)
// 重量级: mark word = 0x... | 10 (末尾 10 = heavyweight)
System.out.println(" [查看 Mark Word 需引入 JOL 依赖]");
}
}
5.4 使用 JOL 工具观察锁状态
<!-- pom.xml 添加依赖 -->
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.17</version>
</dependency>
import org.openjdk.jol.info.ClassLayout;
/**
* 使用 JOL 精确查看对象头中的锁状态
* 运行参数:-XX:BiasedLockingStartupDelay=0
*/
public class JOLDemo {
public static void main(String[] args) throws InterruptedException {
Object obj = new Object();
// 1. 无锁状态
System.out.println("===== 无锁 =====");
System.out.println(ClassLayout.parseInstance(obj).toPrintable());
// 2. 偏向锁
synchronized (obj) {
System.out.println("===== 偏向锁 =====");
System.out.println(ClassLayout.parseInstance(obj).toPrintable());
}
// 3. 轻量级锁(另一个线程交替访问)
Thread t = new Thread(() -> {
synchronized (obj) {
System.out.println("===== 轻量级锁 =====");
System.out.println(ClassLayout.parseInstance(obj).toPrintable());
}
});
t.start();
t.join();
// 4. 重量级锁(多线程竞争)
for (int i = 0; i < 2; i++) {
new Thread(() -> {
synchronized (obj) {
System.out.println("===== 重量级锁 =====");
System.out.println(ClassLayout.parseInstance(obj).toPrintable());
try { Thread.sleep(2000); } catch (InterruptedException ignored) {}
}
}).start();
}
Thread.sleep(3000);
}
}
输出解读(Mark Word 末尾 2~3 bit):
| 锁状态 |
Mark Word 标志 |
含义 |
| 无锁 |
001 |
未加锁 |
| 偏向锁 |
101 |
偏向某个线程 |
| 轻量级锁 |
00 |
栈帧中 CAS 自旋 |
| 重量级锁 |
10 |
操作系统 mutex |
| GC 标记 |
11 |
垃圾回收中 |
六、各类锁速查对照表
6.1 全景对照表
| 分类维度 |
类型 |
核心特征 |
典型实现 |
适用场景 |
| 并发态度 |
悲观锁 |
先加锁再操作 |
synchronized、ReentrantLock、SELECT FOR UPDATE |
写多读少 |
|
乐观锁 |
不加锁,CAS/版本号检测 |
AtomicInteger、数据库 version |
读多写少 |
| 公平性 |
公平锁 |
FIFO 严格排队 |
new ReentrantLock(true) |
需避免饥饿 |
|
非公平锁 |
允许插队 |
synchronized、默认 ReentrantLock |
追求吞吐量 |
| 可重入性 |
可重入锁 |
同一线程可重复获取 |
synchronized、ReentrantLock |
递归/嵌套调用 |
|
不可重入锁 |
重复获取会死锁 |
自定义简易锁 |
特殊场景 |
| 等待策略 |
自旋锁 |
忙等重试 |
CAS 实现 |
锁持有时间极短 |
|
阻塞锁 |
挂起等待唤醒 |
synchronized、AQS |
锁持有时间较长 |
| JVM 锁升级 |
偏向锁 |
单线程,记录 Thread ID |
synchronized 底层 |
无竞争 |
|
轻量级锁 |
多线程交替,CAS 自旋 |
synchronized 底层 |
低竞争 |
|
重量级锁 |
多线程同时竞争 |
synchronized 底层 |
高竞争 |
6.2 选型决策树
你的场景是?
│
├─ 是否需要跨进程? ──→ 是 → 数据库锁 / 分布式锁(Redis/Zookeeper)
│
├─ 并发冲突频繁吗?
│ ├─ 频繁(写多读少) → 悲观锁(synchronized / ReentrantLock)
│ └─ 不频繁(读多写少)→ 乐观锁(CAS / version)
│
├─ 锁持有时间长吗?
│ ├─ 极短(几行代码) → 自旋锁 / CAS
│ └─ 较长(IO/计算) → 阻塞锁
│
├─ 需要公平排队吗?
│ ├─ 需要 → ReentrantLock(true)
│ └─ 不需要 → synchronized / ReentrantLock(false)
│
└─ 有嵌套/递归调用吗?
├─ 有 → 必须用可重入锁(synchronized / ReentrantLock)
└─ 无 → 可重入或不可重入均可
6.3 性能对比总结
吞吐量排名(高 → 低):
乐观锁(CAS) > 自旋锁 > 非公平锁 > 公平锁 > 重量级阻塞锁
延迟排名(低 → 高):
自旋锁 < 乐观锁(CAS) < 非公平锁 ≈ 公平锁 < 重量级阻塞锁
CPU 消耗排名(低 → 高):
重量级阻塞锁 < 公平锁 < 非公平锁 < 乐观锁(CAS) < 自旋锁
附录:完整依赖
<!-- pom.xml -->
<dependencies>
<!-- JOL:查看对象内存布局 -->
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.17</version>
</dependency>
</dependencies>
📌 核心记住一句话:
- 悲观/乐观 → 要不要加锁的问题
- 公平/非公平 → 谁来拿锁的问题
- 可重入/不可重入 → 同线程能不能重复拿的问题
- 自旋/阻塞 → 拿不到锁怎么办的问题
- 偏向/轻量/重量 → JVM 帮你自动选最优策略的问题