深入Java并发:锁机制原理剖析与性能优化实战
📚 文章概述
想象一下,你正在管理一个繁忙的银行🏦:多个客户同时要存取款,但现金柜只有一个。如果没有合适的"锁"
机制,就会出现混乱——两个客户同时取钱,结果账户余额对不上!
在Java并发编程中,锁机制就是这样的"银行管理规则"
🛡️。本文将用生动的比喻和真实场景,深入解析synchronized、Lock、volatile等关键锁机制,让你不仅理解原理,更能直观感受到各种锁的"
性格特点"和使用场景。
🎭 锁的"性格档案"
在深入技术细节之前,让我们先认识一下这些锁的"性格":
锁类型 | 性格特点 | 生活比喻 | 口头禅 |
---|---|---|---|
synchronized | 稳重可靠,JVM优化 | 银行保安👮♂️ | “按规矩办事,简单有效” |
ReentrantLock | 功能丰富,灵活多变 | 智能门锁🔐 | “我可以超时,可以中断,还可以公平竞争” |
ReadWriteLock | 读多写少,效率至上 | 图书馆管理员📚 | “读书可以很多人一起,但写书要排队” |
volatile | 轻量级,保证可见性 | 广播📢 | “我保证消息传达到每个人” |
原子类 | 无锁编程,性能狂魔 | 自动售货机🤖 | “CAS操作,一步到位,绝不冲突” |
1. Java锁机制全景图
🏪 从"商店管理"看锁的分类
想象你开了一家商店,需要管理不同场景下的"访问控制":
- 银行🏦:必须严格排队,一次只能一个人办理业务(悲观锁)
- 图书馆📚:可以多人同时看书,但写书要排队(读写锁)
- 自动售货机🤖:投币就能买,不需要排队(乐观锁)
- VIP通道👑:会员可以插队(公平锁 vs 非公平锁)
现在让我们用这个思路来理解Java锁的分类体系!
1.1 锁的分类体系
1.2 锁的分类详解
1.2.1 按锁的性质分类
锁类型 | 性格特点 | 生活比喻 | 代表实现 | 适用场景 |
---|---|---|---|---|
悲观锁 | 总是担心被抢,先占位再说 | 银行保安👮♂️:“先锁门,再办事” | synchronized、ReentrantLock | 写操作频繁,竞争激烈 |
乐观锁 | 相信大家都很文明,出问题再处理 | 自动售货机🤖:“投币就行,冲突了重试” | AtomicInteger、AtomicReference | 读操作频繁,竞争较少 |
🤔 思考题
为什么银行用悲观锁,而自动售货机用乐观锁?
- 银行:钱很重要,不能出错,宁可慢一点也要安全
- 自动售货机:商品便宜,冲突概率低,追求效率
// 悲观锁示例
public class PessimisticLock {
private int count = 0;
// synchronized悲观锁
public synchronized void increment() {
count++; // 认为会被其他线程修改,先加锁
}
}
// 乐观锁示例
public class OptimisticLock {
private AtomicInteger count = new AtomicInteger(0);
public void increment() {
// 乐观地认为不会被修改,通过CAS操作
count.incrementAndGet();
}
}
1.2.2 按锁的状态分类(synchronized锁升级)
synchronized锁会根据竞争情况自动升级,从无锁→偏向锁→轻量级锁→重量级锁,详细过程见第3章。
1.2.3 按锁的实现分类
实现方式 | 特点 | 代表 | 优势 |
---|---|---|---|
JVM内置锁 | JVM层面实现,自动管理 | synchronized | 简单易用,自动优化 |
API层面锁 | 用户代码控制 | ReentrantLock | 功能丰富,灵活控制 |
// JVM内置锁
public class JVMBuiltinLock {
public synchronized void method1() {
// JVM自动管理锁的获取和释放
}
}
// API层面锁
public class APILock {
private final ReentrantLock lock = new ReentrantLock();
public void method2() {
lock.lock();
try {
// 用户代码控制锁的获取和释放
} finally {
lock.unlock();
}
}
}
1.2.4 按锁的特性分类
特性 | 说明 | 代表实现 | 使用场景 |
---|---|---|---|
可重入锁 | 同一线程可多次获取同一把锁 | synchronized、ReentrantLock | 递归调用、方法调用链 |
不可重入锁 | 同一线程不能重复获取 | 自定义锁 | 特殊业务场景 |
公平锁 | 按请求顺序获取锁 | ReentrantLock(true) | 需要公平性的场景 |
非公平锁 | 不按顺序,允许插队 | synchronized、ReentrantLock(false) | 追求高吞吐量 |
独占锁 | 同时只能有一个线程持有 | synchronized、ReentrantLock | 写操作 |
共享锁 | 多个线程可以同时持有 | ReadWriteLock.readLock() | 读操作 |
互斥锁 | 同一时间只能有一个线程访问 | synchronized | 临界区保护 |
读写锁 | 读读共享,读写互斥,写写互斥 | ReadWriteLock | 读多写少场景 |
public class LockCharacteristicsDemo {
// 可重入锁示例
public synchronized void method1() {
System.out.println("method1获取锁");
method2(); // 可重入调用
}
public synchronized void method2() {
System.out.println("method2获取锁"); // 同一线程可重复获取
}
// 公平锁 vs 非公平锁
private final ReentrantLock fairLock = new ReentrantLock(true); // 公平锁
private final ReentrantLock unfairLock = new ReentrantLock(false); // 非公平锁
// 读写锁示例
private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
public void read() {
rwLock.readLock().lock(); // 共享锁
try {
// 多个线程可以同时读取
} finally {
rwLock.readLock().unlock();
}
}
public void write() {
rwLock.writeLock().lock(); // 独占锁
try {
// 只有一个线程可以写入
} finally {
rwLock.writeLock().unlock();
}
}
}
1.3 锁的选择决策树:如何选择合适的"门卫"?
🎯 选择锁的"黄金法则"
🎭 锁的"性格测试"
场景1:银行取款 💰
- 选择:synchronized(银行保安)
- 原因:简单可靠,JVM优化,适合一般同步需求
场景2:图书馆借书 📚
- 选择:ReadWriteLock(图书馆管理员)
- 原因:多人可以同时看书,但借书要排队
场景3:网站访问计数器 🔢
- 选择:AtomicInteger(自动售货机)
- 原因:高并发,无锁,性能最优
场景4:需要超时控制的资源 ⏰
- 选择:ReentrantLock(智能门卫)
- 原因:支持tryLock,可以设置超时时间
1.4 锁的"能力值"对比
锁类型 | 性能 | 功能 | 易用性 | 适用场景 | 特点 |
---|---|---|---|---|---|
synchronized | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | 一般同步需求 | JVM优化,简单可靠 |
ReentrantLock | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | 复杂同步需求 | 功能丰富,支持超时 |
ReadWriteLock | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ | 读多写少 | 读写分离,高并发读取 |
AtomicInteger | ⭐⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐ | 计数器场景 | 无锁,原子操作 |
LongAdder | ⭐⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐ | 高并发累加 | 高并发优化,最终一致 |
volatile | ⭐⭐⭐⭐⭐ | ⭐ | ⭐⭐⭐⭐ | 状态标志 | 轻量级,保证可见性 |
🏆 各锁的"专长领域"
- synchronized:JVM自动优化,简单可靠,适合一般同步需求
- ReentrantLock:功能丰富,支持超时和中断,适合复杂场景
- ReadWriteLock:读写分离,适合读多写少场景
- 原子类:无锁编程,适合高并发计数
- volatile:轻量级同步,适合状态标志
1.5 学习路径建议:从"新手村"到"大师级"
🎮 锁机制学习"游戏攻略"
第一阶段:新手村(基础阶段)
- synchronized:银行保安👮♂️ - 简单可靠,适合入门
- volatile:广播📢 - 轻量级,理解可见性
- 锁升级:智能门卫🚀 - 理解JVM优化
第二阶段:进阶副本(进阶阶段)
- ReentrantLock:智能门卫🔐 - 功能丰富,灵活控制
- ReadWriteLock:图书馆管理员📚 - 读多写少场景
- 原子类:自动售货机🤖 - 无锁编程,高性能
第三阶段:大师级(实战阶段)
- 综合应用:多锁组合使用
- 性能优化:根据场景选择最优锁
- 最佳实践:避免常见陷阱
🎯 学习建议
- 循序渐进:不要急于求成,先理解基础概念
- 动手实践:每个锁都要写代码验证
- 场景驱动:结合实际业务场景理解锁的选择
- 性能测试:用数据说话,验证不同锁的性能
现在我们对Java锁机制有了全局认识,接下来我们将深入每个具体的锁机制实现。准备好了吗?让我们开始这场"锁的冒险"!🚀
2. synchronized关键字深度解析
🏦 银行保安的"三种工作方式"
synchronized就像银行保安👮♂️,有三种不同的"工作方式"来保护不同的区域:
2.1 synchronized的三种使用方式
2.1.1 同步实例方法:保护"个人办公室" 🏢
public class SynchronizedExample {
private int count = 0;
// 同步实例方法,锁对象是this(当前对象)
// 就像保安保护这个员工的个人办公室
public synchronized void increment() {
count++;
}
// 等价于以下写法
public void increment2() {
synchronized (this) { // 明确指定锁对象
count++;
}
}
}
生活比喻:就像银行保安保护某个员工的个人办公室,只有这个员工可以进入。
2.1.2 同步静态方法:保护"银行总部" 🏛️
public class SynchronizedExample {
private static int staticCount = 0;
// 同步静态方法,锁对象是Class对象
// 就像保安保护整个银行总部
public static synchronized void incrementStatic() {
staticCount++;
}
// 等价于以下写法
public static void incrementStatic2() {
synchronized (SynchronizedExample.class) { // 锁住整个类
staticCount++;
}
}
}
生活比喻:就像银行保安保护整个银行总部,所有员工都要遵守总部的规则。
2.1.3 同步代码块:保护"特定区域" 🔒
public class SynchronizedExample {
private final Object lock = new Object(); // 专用锁对象
private int count = 0;
public void increment() {
// 同步代码块,可以指定任意对象作为锁
// 就像保安保护特定的保险柜
synchronized (lock) {
count++;
}
}
}
生活比喻:就像银行保安保护特定的保险柜,只有持有钥匙的人才能打开。
🤔 思考题
为什么同步代码块比同步方法更灵活?
- 同步方法:锁住整个方法,粒度大
- 同步代码块:只锁住必要的代码,粒度小,性能更好
2.2 synchronized底层原理:保安的"工作手册"
🔍 深入理解:synchronized是如何工作的?
synchronized的底层实现基于JVM的monitor机制,就像银行保安有一套完整的"工作手册":
2.2.1 Monitor对象结构:保安的"工作记录"
每个Java对象都有一个monitor对象,就像每个银行都有保安的工作记录:
// 简化的Monitor对象结构
class Monitor {
Object owner; // 当前持有锁的线程(谁在值班)
int count; // 重入次数(同一线程进入次数)
WaitSet waitSet; // 等待队列(排队等待的线程)
EntryList entryList; // 阻塞队列(被阻塞的线程)
}
生活比喻:
owner
:当前值班的保安count
:保安进入房间的次数(可重入)waitSet
:等待进入的访客队列entryList
:被拦在外面的访客队列
2.2.2 字节码层面分析:保安的"工作流程"
🔍 同步方法的字节码:自动管理
public class SynchronizedBytecode {
private int count = 0;
public synchronized void method() {
count++;
}
}
对应的字节码指令:
// 方法访问标志包含ACC_SYNCHRONIZED
public synchronized void method();
descriptor:()V
flags:ACC_PUBLIC,ACC_SYNCHRONIZED // 注意这个标志
Code:
stack=3,locals=1,args_size=1
0:aload_0
1:dup
2:getfield #2 // Field count:I
5:iconst_1
6:iadd
7:putfield #2 // Field count:I
10:return
生活比喻:就像保安有"自动门禁系统",进入时自动刷卡,离开时自动放行。
🔍 同步代码块的字节码:手动管理
public void synchronizedBlock() {
synchronized (this) {
count++;
}
}
对应字节码:
public void synchronizedBlock();
descriptor:()V
flags:ACC_PUBLIC
Code:
stack=3,locals=3,args_size=1
0:aload_0
1:dup
2:astore_1
3:monitorenter // 获取锁(保安开始值班)
4:aload_0
5:dup
6:getfield #2 // Field count:I
9:iconst_1
10:iadd
11:putfield #2 // Field count:I
14:aload_1
15:monitorexit // 释放锁(保安下班)
16:goto 24
19:astore_2
20:aload_1
21:monitorexit // 异常时也要释放锁(紧急情况也要下班)
22:aload_2
23:athrow
24:return
生活比喻:就像保安手动开关门,进入时手动开门,离开时手动关门,即使发生意外也要确保关门。
🤔 思考题
为什么同步代码块有两个monitorexit?
- 正常情况:第15行的monitorexit
- 异常情况:第21行的monitorexit
- 确保无论什么情况,锁都会被释放
3. 锁升级过程详解
🚀 智能保安的"进化史"
Java 6之后,synchronized引入了锁升级机制,就像银行保安会根据"访客情况"自动调整管理策略,从简单到复杂,以提高性能。
3.1 锁的状态:保安的"工作模式"
Java对象头中的Mark Word记录了锁的状态,就像保安的工作记录:
// 32位JVM中Mark Word的结构
// 锁状态标志位:01-无锁/偏向锁,00-轻量级锁,10-重量级锁,11-GC标记
生活比喻:
- 01:无人值守模式(无锁/偏向锁)
- 00:简单管理模式(轻量级锁)
- 10:严格管理模式(重量级锁)
- 11:特殊状态(GC标记)
3.2 偏向锁(Biased Locking):VIP客户通道 👑
🎯 偏向锁的"性格特点"
偏向锁是JDK 6引入的优化,就像银行给VIP客户开设的专用通道,适用于只有一个线程访问同步块的场景。
3.2.1 偏向锁的获取过程:VIP客户识别
public class BiasedLockExample {
private int count = 0;
public synchronized void method() {
count++; // 偏向锁:第一次访问记录"常客"身份,以后直接放行
}
}
偏向锁获取过程(VIP客户识别流程):
- 检查身份:查看Mark Word中的线程ID是否指向当前线程
- 直接放行:如果是VIP客户,直接执行同步代码
- 身份验证:如果不是,检查偏向锁标志位
- 申请VIP:如果为0,使用CAS操作竞争偏向锁
- 撤销VIP:如果为1,说明存在竞争,撤销偏向锁
生活比喻:
- 银行给经常来的客户办VIP卡
- 以后来银行,直接走VIP通道
- 如果发现不是VIP客户,就撤销VIP资格
3.2.2 偏向锁的撤销:VIP通道关闭
public class BiasedLockRevocation {
private int count = 0;
public synchronized void method1() {
count++; // 第一个线程:申请VIP成功
}
public synchronized void method2() {
count--; // 第二个线程:发现竞争,申请撤销VIP
}
}
偏向锁撤销过程(VIP通道关闭):
- 发现竞争:多个线程竞争同一个锁
- 撤销VIP:偏向锁被撤销
- 升级管理:升级为轻量级锁
生活比喻:
- 银行发现VIP通道被多人使用
- 关闭VIP通道,改为普通通道
- 所有客户都要排队等待
3.3 轻量级锁(Lightweight Locking):简单排队管理 👨💼
🎯 轻量级锁的"性格特点"
轻量级锁适用于线程交替执行同步块的场景,就像小区门卫的简单管理方式。
3.3.1 轻量级锁的获取过程:简单登记
public class LightweightLockExample {
private int count = 0;
public synchronized void method() {
count++; // 轻量级锁:简单登记,没有激烈竞争
}
}
轻量级锁获取过程(简单登记流程):
- 准备登记表:在栈帧中创建Lock Record
- 备份信息:将Mark Word复制到Lock Record的Displaced Mark Word
- 尝试登记:使用CAS操作将Mark Word指向Lock Record
- 登记成功:如果成功,获取轻量级锁
- 升级管理:如果失败,说明存在竞争,升级为重量级锁
生活比喻:
- 小区门卫准备访客登记表
- 备份原来的门禁信息
- 尝试登记新访客
- 如果登记成功,访客可以进入
- 如果登记失败,说明有冲突,需要更严格的管理
3.4 重量级锁(Heavyweight Locking):严格排队管理 👮♂️
🎯 重量级锁的"性格特点"
重量级锁适用于多线程竞争激烈的场景,就像银行大厅的严格管理方式。
3.4.1 重量级锁的实现:严格管理
public class HeavyweightLockExample {
private int count = 0;
public synchronized void method() {
count++; // 重量级锁:严格排队,确保安全
}
}
重量级锁特点(严格管理特点):
- 系统级管理:使用操作系统的mutex实现
- 严格排队:线程阻塞和唤醒需要系统调用
- 性能代价:性能开销较大,但能保证公平性
生活比喻:
- 银行大厅的严格排队管理
- 使用银行总部的管理系统
- 客户要排队等待叫号
- 虽然慢一些,但绝对公平安全
3.5 锁升级示例:保安管理策略的"进化"
public class LockUpgradeDemo {
private int count = 0;
public synchronized void method() {
count++; // 锁会根据访问情况自动升级
}
public static void main(String[] args) throws InterruptedException {
LockUpgradeDemo demo = new LockUpgradeDemo();
// 阶段1:单线程访问,使用偏向锁
demo.method();
// 阶段2:多线程竞争,升级为轻量级锁
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) demo.method();
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) demo.method();
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("Final count: " + demo.count);
}
}
🎭 锁升级的"故事"
银行保安根据访客情况调整管理策略:无锁状态(无人值守)→ 偏向锁(VIP通道)→ 轻量级锁(简单登记)→ 重量级锁(严格排队)。
4. volatile关键字与内存屏障
📢 广播系统:volatile的"工作方式"
volatile是Java提供的一种轻量级的同步机制,就像广播系统📢,主要用于保证变量的可见性和有序性。
4.1 volatile的特性:广播的"三大特点"
volatile关键字具有以下特性:
特性 | 生活比喻 | 技术说明 | 注意事项 |
---|---|---|---|
可见性 | 广播📢:消息传达到每个人 | 一个线程对volatile变量的修改对其他线程立即可见 | 保证数据一致性 |
有序性 | 按顺序广播📻:不能乱序播放 | 禁止指令重排序 | 保证执行顺序 |
不保证原子性 | 广播不能保证操作完成 | 对于复合操作不保证原子性 | 需要配合其他机制 |
🤔 思考题
为什么volatile不保证原子性?
- 广播只能保证消息传达,不能保证接收者完成操作
- 就像广播"开始工作",但不能保证每个人都完成工作
4.2 volatile的实现原理:广播系统的"技术细节"
4.2.1 内存屏障(Memory Barrier):广播的"信号塔"
volatile通过内存屏障实现可见性和有序性,就像广播系统通过信号塔确保消息传达:
public class VolatileExample {
private volatile boolean flag = false; // 广播信号
private int count = 0; // 普通数据
public void writer() {
count = 1; // 普通写操作(本地操作)
flag = true; // volatile写操作,会插入StoreStore屏障(广播信号)
}
public void reader() {
if (flag) { // volatile读操作,会插入LoadLoad屏障(接收广播)
int temp = count; // 普通读操作(本地操作)
}
}
}
生活比喻:volatile写操作通过广播塔发送信号,volatile读操作接收广播信号,确保消息传达。
4.2.2 内存屏障类型:广播系统的"信号类型"
public class MemoryBarrierExample {
private volatile int x = 0; // 广播信号1
private volatile int y = 0; // 广播信号2
private int a = 0; // 普通数据1
private int b = 0; // 普通数据2
// 线程1:发送广播
public void thread1() {
a = 1; // 普通写(本地记录)
x = 1; // volatile写,插入StoreStore屏障(发送广播信号)
}
// 线程2:接收广播
public void thread2() {
y = 1; // volatile写,插入StoreStore屏障(发送广播信号)
b = 1; // 普通写(本地记录)
}
}
🎯 内存屏障的"四种类型"
屏障类型 | 生活比喻 | 技术说明 | 作用 |
---|---|---|---|
LoadLoad | 接收广播前检查 | 读操作之间的屏障 | 确保读操作顺序 |
StoreStore | 发送广播前检查 | 写操作之间的屏障 | 确保写操作顺序 |
LoadStore | 接收后发送 | 读操作和写操作之间的屏障 | 确保读写顺序 |
StoreLoad | 发送后接收 | 写操作和读操作之间的屏障 | 确保写读顺序 |
生活比喻:内存屏障就像广播系统的信号检查机制,确保广播发送和接收的顺序正确。
4.3 volatile的使用场景:广播系统的"应用场景"
4.3.1 状态标志:紧急广播系统 🚨
public class StatusFlag {
private volatile boolean running = true; // 紧急广播信号
public void start() {
new Thread(() -> {
while (running) { // 监听广播信号
// 执行任务
doWork();
}
}).start();
}
public void stop() {
running = false; // 发送紧急广播,其他线程能立即看到这个变化
}
private void doWork() {
// 模拟工作
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
生活比喻:就像紧急广播系统,主控室发送"停止工作"信号,所有工作人员立即停止工作。
4.3.2 双重检查锁定(DCL):智能广播系统 📡
public class Singleton {
private volatile static Singleton instance; // 广播信号:实例状态
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) { // 第一次检查(快速检查)
synchronized (Singleton.class) { // 获取锁(进入临界区)
if (instance == null) { // 第二次检查(安全检查)
instance = new Singleton(); // volatile保证可见性(广播创建完成)
}
}
}
return instance;
}
}
生活比喻:就像智能广播系统,第一次快速检查,进入广播室后再次确认,然后发送广播通知所有人。
🤔 思考题
为什么需要两次检查?
- 第一次检查:快速检查,避免不必要的锁竞争
- 第二次检查:安全检查,确保只有一个实例被创建
4.4 volatile的局限性:广播系统的"限制"
⚠️ 广播系统的"不足之处"
public class VolatileLimitation {
private volatile int count = 0; // 广播信号
// 错误示例:volatile不保证原子性
public void increment() {
count++; // 这个操作不是原子的(广播不能保证操作完成)
}
// 正确示例:使用synchronized保证原子性
public synchronized void incrementSafe() {
count++; // 银行保安确保操作安全
}
// 或者使用原子类
private AtomicInteger atomicCount = new AtomicInteger(0);
public void incrementAtomic() {
atomicCount.incrementAndGet(); // 自动售货机,一步到位
}
}
🎯 volatile的"三大限制"
限制 | 生活比喻 | 技术说明 | 解决方案 |
---|---|---|---|
不保证原子性 | 广播不能保证操作完成 | 复合操作不是原子的 | 使用synchronized或原子类 |
性能开销 | 广播需要消耗电力 | 内存屏障有性能开销 | 合理使用,避免过度使用 |
功能有限 | 广播只能传达消息 | 功能相对简单 | 复杂场景使用其他锁机制 |
🤔 思考题
什么时候使用volatile?
- 适合使用:状态标志、双重检查锁定
- 不适合使用:需要原子性的复合操作
- 选择原则:简单状态用volatile,复杂操作用synchronized
5. ReentrantLock与ReadWriteLock
🔐 智能门卫:ReentrantLock的"超能力"
ReentrantLock是Java 5引入的可重入锁,就像智能门卫🔐,提供了比synchronized更灵活的锁机制。
5.1 ReentrantLock详解:智能门卫的"功能特点"
🎯 ReentrantLock vs synchronized 对比
特性 | synchronized | ReentrantLock | 生活比喻 |
---|---|---|---|
可重入性 | ✅ 支持 | ✅ 支持 | 同一员工可以多次进入办公室 |
超时控制 | ❌ 不支持 | ✅ 支持 | 智能门卫可以设置等待时间 |
可中断性 | ❌ 不支持 | ✅ 支持 | 智能门卫可以被中断 |
公平性 | ❌ 非公平 | ✅ 可配置 | 智能门卫可以公平排队 |
条件等待 | ❌ 不支持 | ✅ 支持 | 智能门卫可以设置等待条件 |
5.1.1 基本使用:智能门卫的"标准操作"
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockExample {
private final ReentrantLock lock = new ReentrantLock(); // 智能门卫
private int count = 0;
public void increment() {
lock.lock(); // 智能门卫开始值班
try {
count++; // 执行任务
} finally {
lock.unlock(); // 智能门卫下班(必须在finally中释放锁)
}
}
public int getCount() {
lock.lock(); // 智能门卫开始值班
try {
return count; // 查看数据
} finally {
lock.unlock(); // 智能门卫下班
}
}
}
生活比喻:
- 智能门卫开始值班:
lock.lock()
- 执行任务:在保护区域内工作
- 智能门卫下班:
lock.unlock()
- 必须下班:
finally
确保门卫一定会下班
5.1.2 可重入性演示:智能门卫的"VIP特权"
public class ReentrantLockDemo {
private final ReentrantLock lock = new ReentrantLock(); // 智能门卫
public void method1() {
lock.lock(); // 智能门卫开始值班
try {
System.out.println("method1获取锁 - 进入办公室");
method2(); // 可重入:同一员工可以进入其他房间
} finally {
lock.unlock(); // 智能门卫下班
}
}
public void method2() {
lock.lock(); // 智能门卫继续值班(同一员工)
try {
System.out.println("method2获取锁 - 进入会议室");
} finally {
lock.unlock(); // 智能门卫下班
}
}
}
生活比喻:员工有VIP卡,可以进入多个房间,智能门卫识别是同一员工,允许进入。
5.1.3 公平锁与非公平锁:智能门卫的"排队策略"
public class FairLockExample {
// 公平锁:按顺序排队
private final ReentrantLock fairLock = new ReentrantLock(true);
// 非公平锁:允许插队(默认)
private final ReentrantLock unfairLock = new ReentrantLock(false);
public void fairMethod() {
fairLock.lock(); // 公平门卫:按顺序排队
try {
System.out.println("公平锁执行 - 按顺序排队");
} finally {
fairLock.unlock();
}
}
public void unfairMethod() {
unfairLock.lock(); // 非公平门卫:允许插队
try {
System.out.println("非公平锁执行 - 允许插队");
} finally {
unfairLock.unlock();
}
}
}
🎯 公平锁 vs 非公平锁 对比
特性 | 公平锁 | 非公平锁 | 生活比喻 |
---|---|---|---|
排队方式 | 按顺序排队 | 允许插队 | 银行排队 vs 地铁排队 |
性能 | 较低 | 较高 | 公平但慢 vs 不公平但快 |
适用场景 | 需要公平性 | 追求高吞吐量 | 重要业务 vs 高并发场景 |
默认选择 | 需要手动设置 | 默认模式 | 特殊需求 vs 一般需求 |
生活比喻:公平锁像银行排队(先来先服务),非公平锁像地铁排队(可以插队)。
5.1.4 尝试获取锁:智能门卫的"灵活策略"
public class TryLockExample {
private final ReentrantLock lock = new ReentrantLock(); // 智能门卫
public void tryLockMethod() {
if (lock.tryLock()) { // 尝试获取锁(不等待)
try {
System.out.println("成功获取锁 - 门卫允许进入");
// 执行业务逻辑
} finally {
lock.unlock(); // 释放锁
}
} else {
System.out.println("获取锁失败,执行其他逻辑 - 门卫拒绝,去其他地方");
}
}
public void tryLockWithTimeout() {
try {
if (lock.tryLock(1, TimeUnit.SECONDS)) { // 尝试获取锁(等待1秒)
try {
System.out.println("在1秒内成功获取锁 - 门卫在1秒内允许进入");
} finally {
lock.unlock();
}
} else {
System.out.println("1秒内未获取到锁 - 门卫1秒内没有允许进入");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 处理中断
}
}
}
🎯 尝试获取锁的"三种策略"
策略 | 方法 | 生活比喻 | 适用场景 |
---|---|---|---|
立即尝试 | tryLock() |
门卫立即决定 | 不等待,失败就做其他事 |
超时等待 | tryLock(time, unit) |
门卫等待一定时间 | 可以等待,但有限制 |
阻塞等待 | lock() |
门卫一直等待 | 必须获取锁,可以无限等待 |
生活比喻:立即尝试(门卫立即决定),超时等待(门卫等待1分钟),阻塞等待(门卫一直等待)。
5.2 ReadWriteLock详解:图书馆管理员 📚
🎯 ReadWriteLock的"管理策略"
ReadWriteLock就像图书馆管理员📚,允许多个读者同时看书,但写书要排队。
5.2.1 基本使用:图书馆的"管理规则"
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockExample {
private final ReadWriteLock lock = new ReentrantReadWriteLock(); // 图书馆管理员
private int value = 0; // 图书馆的书籍
public int read() {
lock.readLock().lock(); // 获取读锁(允许多人同时看书)
try {
return value; // 查看书籍
} finally {
lock.readLock().unlock(); // 释放读锁
}
}
public void write(int newValue) {
lock.writeLock().lock(); // 获取写锁(写书要排队)
try {
value = newValue; // 修改书籍
} finally {
lock.writeLock().unlock(); // 释放写锁
}
}
}
🎯 ReadWriteLock的"三大规则"
规则 | 生活比喻 | 技术说明 | 性能特点 |
---|---|---|---|
读读共享 | 多人可以同时看书 | 多个线程可以同时获取读锁 | 高并发读取 |
读写互斥 | 看书时不能写书 | 读锁和写锁互斥 | 保证数据一致性 |
写写互斥 | 写书要排队 | 写锁之间互斥 | 保证写操作安全 |
生活比喻:读读共享(多人同时看书),读写互斥(看书时不能写书),写写互斥(写书要排队)。
5.2.2 读写锁性能对比:图书馆的"效率测试"
public class ReadWriteLockPerformance {
private final ReadWriteLock lock = new ReentrantReadWriteLock(); // 图书馆管理员
private final Map<String, String> cache = new HashMap<>(); // 图书馆的书籍
public String get(String key) {
lock.readLock().lock(); // 获取读锁(多人可以同时看书)
try {
return cache.get(key); // 查看书籍
} finally {
lock.readLock().unlock();
}
}
public void put(String key, String value) {
lock.writeLock().lock(); // 获取写锁(写书要排队)
try {
cache.put(key, value); // 修改书籍
} finally {
lock.writeLock().unlock();
}
}
// 性能测试:图书馆的"效率测试"
public static void main(String[] args) throws InterruptedException {
ReadWriteLockPerformance demo = new ReadWriteLockPerformance();
// 写入测试:写书测试
long start = System.currentTimeMillis();
for (int i = 0; i < 1000; i++) {
demo.put("key" + i, "value" + i);
}
long writeTime = System.currentTimeMillis() - start;
// 读取测试:看书测试
start = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
demo.get("key" + (i % 1000));
}
long readTime = System.currentTimeMillis() - start;
System.out.println("写入时间: " + writeTime + "ms");
System.out.println("读取时间: " + readTime + "ms");
System.out.println("读多写少场景,ReadWriteLock性能优势明显!");
}
}
🎯 ReadWriteLock vs synchronized 性能对比
场景 | ReadWriteLock | synchronized | 性能差异 | 生活比喻 |
---|---|---|---|---|
读多写少 | 高并发读取 | 串行读取 | ReadWriteLock优势明显 | 图书馆 vs 单人阅览室 |
写多读少 | 写操作串行 | 写操作串行 | 性能相近 | 写书都要排队 |
读写均衡 | 中等性能 | 中等性能 | 性能相近 | 平衡场景 |
生活比喻:读多写少(图书馆),写多读少(出版社),读写均衡(学校)。
6. 原子操作类详解
🤖 自动售货机:原子操作类的"无锁世界"
Java提供了丰富的原子操作类,位于java.util.concurrent.atomic包中,就像自动售货机🤖,用于实现无锁的线程安全操作。
6.1 基本原子类型:自动售货机的"商品类型"
6.1.1 AtomicInteger:整数自动售货机 🔢
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicIntegerExample {
private final AtomicInteger count = new AtomicInteger(0); // 整数自动售货机
public void increment() {
count.incrementAndGet(); // 原子递增(投币买商品)
}
public void decrement() {
count.decrementAndGet(); // 原子递减(退币)
}
public int get() {
return count.get(); // 获取当前值(查看库存)
}
public void set(int value) {
count.set(value); // 设置值(补货)
}
public boolean compareAndSet(int expect, int update) {
return count.compareAndSet(expect, update); // CAS操作(智能交易)
}
}
🎯 原子操作类的"三大特点"
特点 | 生活比喻 | 技术说明 | 优势 |
---|---|---|---|
无锁 | 自动售货机不需要人工 | 使用CAS操作,无需锁 | 高性能,无阻塞 |
原子性 | 投币买商品,一步到位 | 操作不可分割 | 保证数据一致性 |
线程安全 | 多人同时使用售货机 | 多线程安全 | 并发安全 |
生活比喻:自动售货机不需要人工管理,投币买商品一步完成,多人同时使用不会出错。
6.1.2 AtomicLong:长整数自动售货机 🔢
import java.util.concurrent.atomic.AtomicLong;
public class AtomicLongExample {
private final AtomicLong counter = new AtomicLong(0); // 长整数自动售货机
public long increment() {
return counter.incrementAndGet(); // 原子递增(投币买商品)
}
public long add(long delta) {
return counter.addAndGet(delta); // 原子加法(批量投币)
}
public long get() {
return counter.get(); // 获取当前值(查看库存)
}
}
6.1.3 AtomicBoolean:布尔值自动售货机 ✅❌
import java.util.concurrent.atomic.AtomicBoolean;
public class AtomicBooleanExample {
private final AtomicBoolean flag = new AtomicBoolean(false); // 布尔值自动售货机
public boolean setTrue() {
return flag.compareAndSet(false, true); // 设置为true(投币买商品)
}
public boolean setFalse() {
return flag.compareAndSet(true, false); // 设置为false(退币)
}
public boolean get() {
return flag.get(); // 获取当前值(查看状态)
}
}
🎯 原子操作类的"家族成员"
类型 | 生活比喻 | 适用场景 | 特点 |
---|---|---|---|
AtomicInteger | 整数自动售货机 | 计数器、索引 | 最常用 |
AtomicLong | 长整数自动售货机 | 大数值计数 | 支持更大数值 |
AtomicBoolean | 布尔值自动售货机 | 状态标志 | 开关控制 |
AtomicReference | 引用自动售货机 | 对象引用 | 引用类型 |
LongAdder | 高并发累加器 | 高并发计数 | 性能最优 |
生活比喻:AtomicInteger(普通售货机),AtomicLong(大容量售货机),AtomicBoolean(开关售货机),AtomicReference(复杂商品售货机),LongAdder(高并发售货机)。
6.2 原子引用类型:复杂商品自动售货机 🎁
6.2.1 AtomicReference:引用自动售货机
import java.util.concurrent.atomic.AtomicReference;
public class AtomicReferenceExample {
private final AtomicReference<String> value = new AtomicReference<>("initial"); // 引用自动售货机
public void updateValue(String newValue) {
String oldValue = value.get(); // 获取当前商品
while (!value.compareAndSet(oldValue, newValue)) { // 尝试更换商品
oldValue = value.get(); // 重新获取最新商品(可能被其他人换了)
}
}
public String getValue() {
return value.get(); // 获取当前商品
}
}
生活比喻:自动售货机里的商品可以被更换,投币时检查商品是否还是原来的,如果被换了就重新检查。
6.2.2 原子更新字段:远程控制自动售货机 🎮
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
public class AtomicFieldUpdaterExample {
private volatile int count = 0; // 远程控制的商品数量
private static final AtomicIntegerFieldUpdater<AtomicFieldUpdaterExample> updater =
AtomicIntegerFieldUpdater.newUpdater(AtomicFieldUpdaterExample.class, "count");
public void increment() {
updater.incrementAndGet(this); // 远程控制增加商品
}
public int getCount() {
return count; // 查看商品数量
}
}
生活比喻:远程控制自动售货机,通过远程控制器更新商品数量,节省内存,提高性能。
6.3 原子数组类型:多商品自动售货机 🏪
import java.util.concurrent.atomic.AtomicIntegerArray;
public class AtomicArrayExample {
private final AtomicIntegerArray array = new AtomicIntegerArray(10); // 多商品自动售货机
public void set(int index, int value) {
array.set(index, value); // 设置指定位置的商品数量
}
public int get(int index) {
return array.get(index); // 获取指定位置的商品数量
}
public int incrementAndGet(int index) {
return array.incrementAndGet(index); // 增加指定位置的商品数量
}
public boolean compareAndSet(int index, int expect, int update) {
return array.compareAndSet(index, expect, update); // 智能更新指定位置的商品
}
}
生活比喻:多商品自动售货机,每个位置都有独立的商品,可以独立操作每个位置。
6.4 原子累加器:高并发自动售货机 🚀
import java.util.concurrent.atomic.LongAdder;
public class LongAdderExample {
private final LongAdder adder = new LongAdder(); // 高并发自动售货机
public void increment() {
adder.increment(); // 增加一个商品(高并发优化)
}
public void add(long value) {
adder.add(value); // 增加指定数量的商品
}
public long sum() {
return adder.sum(); // 查看总商品数量
}
public void reset() {
adder.reset(); // 重置商品数量
}
public long sumThenReset() {
return adder.sumThenReset(); // 查看并重置商品数量
}
}
🎯 LongAdder vs AtomicLong 性能对比
特性 | LongAdder | AtomicLong | 生活比喻 |
---|---|---|---|
高并发性能 | 优秀 | 一般 | 高并发自动售货机 vs 普通自动售货机 |
内存占用 | 较高 | 较低 | 多通道 vs 单通道 |
适用场景 | 高并发累加 | 一般计数 | 网站访问统计 vs 简单计数 |
精度 | 最终一致 | 强一致 | 最终准确 vs 实时准确 |
生活比喻:LongAdder(高并发售货机,多通道工作),AtomicLong(普通售货机,单通道工作)。
6.5 原子操作类性能对比:自动售货机的"效率测试"
public class AtomicPerformanceTest {
private static final int THREAD_COUNT = 10; // 10个客户
private static final int OPERATION_COUNT = 1000000; // 每个客户买100万次商品
public static void main(String[] args) throws InterruptedException {
System.out.println("🚀 开始性能测试:10个客户,每个客户买100万次商品");
// synchronized测试:银行保安管理
testSynchronized();
// AtomicInteger测试:普通自动售货机
testAtomicInteger();
// LongAdder测试:高并发自动售货机
testLongAdder();
}
private static void testSynchronized() throws InterruptedException {
final int[] count = {0};
final Object lock = new Object(); // 银行保安
long start = System.currentTimeMillis();
Thread[] threads = new Thread[THREAD_COUNT];
for (int i = 0; i < THREAD_COUNT; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < OPERATION_COUNT; j++) {
synchronized (lock) { // 银行保安管理
count[0]++;
}
}
});
threads[i].start();
}
for (Thread thread : threads) {
thread.join();
}
long time = System.currentTimeMillis() - start;
System.out.println("🏦 Synchronized (银行保安): " + time + "ms, count: " + count[0]);
}
private static void testAtomicInteger() throws InterruptedException {
final AtomicInteger count = new AtomicInteger(0); // 普通自动售货机
long start = System.currentTimeMillis();
Thread[] threads = new Thread[THREAD_COUNT];
for (int i = 0; i < THREAD_COUNT; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < OPERATION_COUNT; j++) {
count.incrementAndGet(); // 普通自动售货机
}
});
threads[i].start();
}
for (Thread thread : threads) {
thread.join();
}
long time = System.currentTimeMillis() - start;
System.out.println("🤖 AtomicInteger (普通自动售货机): " + time + "ms, count: " + count.get());
}
private static void testLongAdder() throws InterruptedException {
final LongAdder adder = new LongAdder(); // 高并发自动售货机
long start = System.currentTimeMillis();
Thread[] threads = new Thread[THREAD_COUNT];
for (int i = 0; i < THREAD_COUNT; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < OPERATION_COUNT; j++) {
adder.increment(); // 高并发自动售货机
}
});
threads[i].start();
}
for (Thread thread : threads) {
thread.join();
}
long time = System.currentTimeMillis() - start;
System.out.println("🚀 LongAdder (高并发自动售货机): " + time + "ms, count: " + adder.sum());
}
}
🎯 性能测试结果分析
6.5.1 典型测试结果数据
在10个线程,每个线程执行100万次操作的测试环境下,典型结果如下:
锁机制 | 执行时间 | 相对性能 | 内存占用 | 适用场景 |
---|---|---|---|---|
synchronized | 2000-3000ms | 基准 | 低 | 一般同步需求 |
AtomicInteger | 800-1200ms | 2-3倍 | 低 | 简单原子操作 |
LongAdder | 400-600ms | 4-6倍 | 中等 | 高并发累加 |
6.5.2 性能差异深度分析
🔍 synchronized性能特点:
// synchronized的性能瓶颈
synchronized (lock){
count++; // 每次操作都需要获取和释放锁
}
- 优势:JVM自动优化,简单可靠,内存占用低
- 劣势:每次操作都需要获取锁,高并发下竞争激烈
- 适用场景:低到中等并发,简单同步需求
🚀 AtomicInteger性能特点:
// AtomicInteger的CAS操作
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
- 优势:无锁操作,CAS机制,性能较好
- 劣势:高并发下CAS失败率高,需要重试
- 适用场景:中等并发,简单原子操作
⚡ LongAdder性能特点:
// LongAdder的分段累加策略
public void increment() {
Cell[] as;
long b, v;
int m;
Cell a;
if ((as = cells) != null || !casBase(b = base, b + 1)) {
// 使用分段累加,减少竞争
}
}
- 优势:分段累加,减少竞争,高并发性能最优
- 劣势:内存占用较高,最终一致性
- 适用场景:高并发累加,对实时性要求不高
6.5.3 不同并发度下的性能对比
线程数 | synchronized | AtomicInteger | LongAdder | 推荐选择 |
---|---|---|---|---|
1-2个 | 1000ms | 800ms | 600ms | synchronized |
4-8个 | 2000ms | 1000ms | 500ms | AtomicInteger |
16+个 | 4000ms | 2000ms | 400ms | LongAdder |
6.5.4 性能选择决策树
6.5.5 实际应用建议
🎯 选择原则:
简单计数场景:优先考虑synchronized
// 适合:访问量不大的计数器 private int viewCount = 0; public synchronized void incrementView() { viewCount++; }
中等并发场景:选择AtomicInteger
// 适合:中等并发的业务计数器 private AtomicInteger orderCount = new AtomicInteger(0); public void createOrder() { orderCount.incrementAndGet(); }
高并发场景:选择LongAdder
// 适合:高并发的统计场景 private LongAdder totalRequests = new LongAdder(); public void handleRequest() { totalRequests.increment(); }
⚠️ 注意事项:
- LongAdder的最终一致性:
sum()
方法返回的是近似值 - 内存考虑:LongAdder会创建多个Cell对象,内存占用较高
- 实时性要求:如果需要实时准确值,选择AtomicInteger
- 业务场景:根据实际业务需求选择合适的锁机制
6.5.6 性能优化技巧
🔧 减少锁竞争:
// 不好:锁粒度太大
public synchronized void processData() {
// 大量不需要同步的代码
doHeavyWork();
// 只有这一行需要同步
count++;
}
// 好:锁粒度小
public void processData() {
doHeavyWork();
synchronized (this) {
count++;
}
}
🚀 使用无锁数据结构:
// 使用ConcurrentHashMap替代synchronized Map
private final Map<String, String> cache = new ConcurrentHashMap<>();
⚡ 批量操作优化:
// 批量累加,减少CAS操作次数
public void batchIncrement(int times) {
for (int i = 0; i < times; i++) {
adder.increment();
}
}
7. 实战案例:高性能缓存实现
🏪 智能商店:高性能缓存的"商业案例"
7.1 需求分析:智能商店的"经营需求"
设计一个高性能的缓存系统,就像开一家智能商店🏪,要求:
需求 | 生活比喻 | 技术实现 | 重要性 |
---|---|---|---|
支持并发读写 | 多个顾客同时购物 | 读写锁机制 | ⭐⭐⭐⭐⭐ |
支持缓存过期 | 商品有保质期 | 时间戳管理 | ⭐⭐⭐⭐ |
支持LRU淘汰 | 滞销商品下架 | 最近最少使用 | ⭐⭐⭐⭐ |
线程安全 | 商店管理规范 | 锁机制保护 | ⭐⭐⭐⭐⭐ |
高性能 | 快速结账 | 优化算法 | ⭐⭐⭐⭐⭐ |
7.2 设计方案:智能商店的"技术架构"
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class HighPerformanceCache<K, V> {
// 缓存数据存储:商店的货架
private final ConcurrentHashMap<K, CacheEntry<V>> cache = new ConcurrentHashMap<>();
// 读写锁,用于保护LRU链表:商店的管理规则
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private final ReentrantReadWriteLock.ReadLock readLock = lock.readLock();
private final ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();
// LRU链表头尾节点:商品摆放顺序
private volatile Node<K> head;
private volatile Node<K> tail;
// 缓存配置:商店的经营参数
private final int maxSize; // 最大商品数量
private final long expireTime; // 商品保质期
private final AtomicInteger size = new AtomicInteger(0); // 当前商品数量
// 清理过期缓存的线程池:定期清理员工
private final ScheduledExecutorService cleanupExecutor =
Executors.newSingleThreadScheduledExecutor();
public HighPerformanceCache(int maxSize, long expireTime) {
this.maxSize = maxSize;
this.expireTime = expireTime;
// 初始化LRU链表:设置商品摆放区域
head = new Node<>(null);
tail = new Node<>(null);
head.next = tail;
tail.prev = head;
// 启动清理任务:定期清理过期商品
cleanupExecutor.scheduleAtFixedRate(this::cleanupExpired,
1, 1, TimeUnit.SECONDS);
}
// 缓存条目:商店的商品
private static class CacheEntry<V> {
private final V value; // 商品内容
private final long createTime; // 商品上架时间
private volatile long lastAccessTime; // 最后访问时间
public CacheEntry(V value) {
this.value = value;
this.createTime = System.currentTimeMillis();
this.lastAccessTime = createTime;
}
public V getValue() {
lastAccessTime = System.currentTimeMillis(); // 更新访问时间
return value;
}
public boolean isExpired(long expireTime) {
return System.currentTimeMillis() - createTime > expireTime; // 检查是否过期
}
}
// LRU链表节点:商品摆放位置
private static class Node<K> {
private K key; // 商品标识
private Node<K> prev; // 前一个商品
private Node<K> next; // 后一个商品
public Node(K key) {
this.key = key;
}
}
// 获取缓存:顾客购买商品
public V get(K key) {
CacheEntry<V> entry = cache.get(key); // 查找商品
if (entry == null) {
return null; // 商品不存在
}
if (entry.isExpired(expireTime)) { // 检查商品是否过期
remove(key); // 移除过期商品
return null;
}
// 更新LRU链表:将商品移到最前面
updateLRU(key);
return entry.getValue(); // 返回商品
}
// 设置缓存:商店上架商品
public void put(K key, V value) {
CacheEntry<V> newEntry = new CacheEntry<>(value); // 创建新商品
CacheEntry<V> oldEntry = cache.put(key, newEntry); // 上架商品
if (oldEntry == null) {
// 新增条目:新商品上架
int currentSize = size.incrementAndGet();
if (currentSize > maxSize) {
evictLRU(); // 商品太多,下架滞销商品
}
}
// 更新LRU链表:将商品移到最前面
updateLRU(key);
}
// 删除缓存:商店下架商品
public V remove(K key) {
CacheEntry<V> entry = cache.remove(key); // 下架商品
if (entry != null) {
size.decrementAndGet(); // 减少商品数量
removeFromLRU(key); // 从摆放顺序中移除
}
return entry != null ? entry.getValue() : null;
}
// 更新LRU链表:调整商品摆放顺序
private void updateLRU(K key) {
writeLock.lock(); // 获取写锁(调整摆放顺序需要独占)
try {
// 先从链表中移除:从当前位置移除
removeFromLRU(key);
// 添加到头部:放到最前面(最近使用)
Node<K> newNode = new Node<>(key);
newNode.next = head.next;
newNode.prev = head;
head.next.prev = newNode;
head.next = newNode;
} finally {
writeLock.unlock();
}
}
// 从LRU链表中移除
private void removeFromLRU(K key) {
Node<K> current = head.next;
while (current != tail) {
if (current.key.equals(key)) {
current.prev.next = current.next;
current.next.prev = current.prev;
break;
}
current = current.next;
}
}
// LRU淘汰
private void evictLRU() {
writeLock.lock();
try {
if (tail.prev != head) {
K keyToRemove = tail.prev.key;
remove(keyToRemove);
}
} finally {
writeLock.unlock();
}
}
// 清理过期缓存
private void cleanupExpired() {
cache.entrySet().removeIf(entry -> {
if (entry.getValue().isExpired(expireTime)) {
size.decrementAndGet();
removeFromLRU(entry.getKey());
return true;
}
return false;
});
}
// 获取缓存统计信息
public CacheStats getStats() {
return new CacheStats(size.get(), cache.size());
}
// 缓存统计信息
public static class CacheStats {
private final int size;
private final int actualSize;
public CacheStats(int size, int actualSize) {
this.size = size;
this.actualSize = actualSize;
}
public int getSize() {
return size;
}
public int getActualSize() {
return actualSize;
}
}
// 关闭缓存
public void shutdown() {
cleanupExecutor.shutdown();
}
}
7.3 性能测试
public class CachePerformanceTest {
public static void main(String[] args) throws InterruptedException {
HighPerformanceCache<String, String> cache =
new HighPerformanceCache<>(1000, 5000);
// 写入测试
long start = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
cache.put("key" + i, "value" + i);
}
long writeTime = System.currentTimeMillis() - start;
// 读取测试
start = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
cache.get("key" + i);
}
long readTime = System.currentTimeMillis() - start;
// 并发测试
start = System.currentTimeMillis();
ExecutorService executor = Executors.newFixedThreadPool(10);
CountDownLatch latch = new CountDownLatch(1000);
for (int i = 0; i < 1000; i++) {
final int index = i;
executor.submit(() -> {
cache.put("concurrent_key" + index, "concurrent_value" + index);
cache.get("concurrent_key" + index);
latch.countDown();
});
}
latch.await();
long concurrentTime = System.currentTimeMillis() - start;
System.out.println("写入时间: " + writeTime + "ms");
System.out.println("读取时间: " + readTime + "ms");
System.out.println("并发操作时间: " + concurrentTime + "ms");
System.out.println("缓存统计: " + cache.getStats());
cache.shutdown();
executor.shutdown();
}
}
8. 总结与最佳实践
8.1 锁机制选择指南
场景 | 推荐方案 | 原因 |
---|---|---|
简单同步 | synchronized | 简单易用,JVM优化 |
需要超时 | ReentrantLock | 支持tryLock |
读多写少 | ReadWriteLock | 提高读性能 |
计数器 | AtomicInteger | 无锁,高性能 |
累加器 | LongAdder | 高并发场景最优 |
8.2 性能优化建议
减少锁的粒度
// 不好:锁整个方法 public synchronized void method() { // 大量代码 } // 好:只锁必要的代码块 public void method() { // 不需要同步的代码 synchronized(this) { // 需要同步的代码 } // 不需要同步的代码 }
使用无锁数据结构
// 使用ConcurrentHashMap替代synchronized Map private final Map<String, String> cache = new ConcurrentHashMap<>();
避免锁嵌套
// 避免死锁 public void method1() { synchronized(lock1) { synchronized(lock2) { // 业务逻辑 } } }
8.3 常见陷阱与解决方案
volatile不保证原子性
// 错误 private volatile int count = 0; public void increment() { count++; // 不是原子操作 } // 正确 private final AtomicInteger count = new AtomicInteger(0); public void increment() { count.incrementAndGet(); }
忘记释放锁
// 错误 public void method() { lock.lock(); // 如果这里抛异常,锁不会被释放 doSomething(); lock.unlock(); } // 正确 public void method() { lock.lock(); try { doSomething(); } finally { lock.unlock(); } }
锁对象选择错误
// 错误:使用String作为锁对象 private final String lock = "lock"; // 正确:使用专用对象 private final Object lock = new Object();
8.4 监控与调试
- 使用JConsole监控线程状态
- 使用JProfiler分析锁竞争
- 使用Arthas诊断死锁
// 死锁检测示例
public class DeadlockDetector {
public static void detectDeadlock() {
ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
long[] deadlockedThreads = threadBean.findDeadlockedThreads();
if (deadlockedThreads != null) {
ThreadInfo[] threadInfos = threadBean.getThreadInfo(deadlockedThreads);
for (ThreadInfo threadInfo : threadInfos) {
System.out.println("死锁线程: " + threadInfo.getThreadName());
System.out.println("等待锁: " + threadInfo.getLockName());
}
}
}
}
8.5 学习资源推荐
8.5.1 官方文档与规范
📚 Oracle官方资源
🔍 OpenJDK源码
8.5.2 深度技术文章
🔬 锁机制深度解析
⚡ 性能优化相关
🏗️ 实战案例分析
8.5.3 开源项目学习
🚀 优秀开源项目
📖 源码学习建议
- 从简单的AtomicInteger开始,理解CAS原理
- 研究ConcurrentHashMap的实现,学习分段锁
- 分析ThreadPoolExecutor源码,理解线程池机制
🎯 总结
本文深入解析了Java并发编程中的核心锁机制:
- synchronized:JVM内置锁,简单易用,适合大多数场景
- 锁升级:从偏向锁到重量级锁的优化过程
- volatile:轻量级同步,保证可见性和有序性
- ReentrantLock:可重入锁,提供更灵活的锁控制
- ReadWriteLock:读写分离,提高读性能
- 原子操作类:无锁编程,高性能计数器
通过实战案例,我们实现了一个高性能缓存系统,展示了各种锁机制的综合应用。