Java锁机制完全拆解:从synchronized到分布式锁的终极生存指南
——高并发场景下,如何用一把锁守住你的代码贞操?
一、锁的本质:多线程世界的“道德与法治”
当多个线程同时抢夺资源时,锁就是维持秩序的审判者:
经典翻车现场:
10个线程同时给账户余额+100,结果只加了200
库存超卖:100人抢购90件商品,系统显示-10库存
锁的核心使命:
原子性:把“余额=余额+100”变成不可分割的操作
可见性:强制线程从主内存读取最新值,而非缓存副本
有序性:禁止指令重排序引发的诡异BUG
二、单机锁宇宙:从JVM锁到CAS无锁编程
1. synchronized:原始但强大的监视器锁
java
Copy Code
// 对象锁(锁住this实例)
public synchronized void transfer() {
// 临界区代码
}
// 类锁(锁住Class对象)
public static synchronized void staticMethod() {
// 临界区代码
}
// 同步代码块(锁住任意对象)
Object lock = new Object();
public void method() {
synchronized(lock) {
// 临界区代码
}
}
升级路线:无锁 → 偏向锁 → 轻量级锁 → 重量级锁(涉及对象头Mark Word操作)
锁膨胀代价:重量级锁需要CPU从用户态切换到内核态,耗时约1微秒
2. ReentrantLock:可定制的显式锁
java
Copy Code
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
void method() {
lock.lock();
try {
while(!conditionMet) {
condition.await(); // 释放锁并等待
}
// 临界区代码
condition.signalAll();
} finally {
lock.unlock();
}
}
性能对比:
场景 synchronized ReentrantLock
低竞争 更快 稍慢
高竞争 可能死锁 支持公平锁
超时等待 不支持 tryLock(3, TimeUnit.SECONDS)
3. CAS无锁编程:Compare And Swap的魔法
java
Copy Code
AtomicInteger balance = new AtomicInteger(100);
// 无锁实现余额增减
public void add(int delta) {
while(true) {
int oldVal = balance.get();
int newVal = oldVal + delta;
if(balance.compareAndSet(oldVal, newVal)) {
break;
}
}
}
ABA问题解决方案:使用AtomicStampedReference带版本号
三、高阶锁策略:应对复杂战场
1. 读写锁(ReadWriteLock)
java
Copy Code
ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
Lock readLock = rwLock.readLock();
Lock writeLock = rwLock.writeLock();
// 读操作(共享锁)
void getData() {
readLock.lock();
try { /* 读取操作 */ }
finally { readLock.unlock(); }
}
// 写操作(独占锁)
void updateData() {
writeLock.lock();
try { /* 写入操作 */ }
finally { writeLock.unlock(); }
}
适用场景:读多写少(如缓存系统)
2. 自旋锁(SpinLock)
java
Copy Code
public class SpinLock {
private AtomicBoolean locked = new AtomicBoolean(false);
public void lock() {
while(!locked.compareAndSet(false, true)) {
// 自旋等待(适合短时间锁定)
}
}
public void unlock() {
locked.set(false);
}
}
优化技巧:JDK的LongAdder就是自旋锁+CAS的产物
3. 分段锁(Segment Lock)
ConcurrentHashMap实现精髓:
java
Copy Code
// 默认分成16个Segment,每个Segment独立加锁
static final class Segment
volatile HashEntry
}
降低锁粒度:把全局锁变成多个局部锁,减少竞争
四、分布式锁:跨JVM的生死狙击
1. Redis分布式锁(Redisson实现)
java
Copy Code
RLock lock = redisson.getLock("orderLock");
lock.lock();
try {
// 业务代码
} finally {
lock.unlock();
}
核心机制:
加锁Lua脚本保证原子性
WatchDog自动续期防止锁过期
红锁算法(RedLock)应对主从故障
2. Zookeeper分布式锁
java
Copy Code
InterProcessMutex lock = new InterProcessMutex(client, "/locks/order");
lock.acquire();
try {
// 业务代码
} finally {
lock.release();
}
底层原理:
创建临时有序节点
判断自己是否是最小节点
监听前序节点删除事件
3. 分布式锁选型对比
维度 Redis Zookeeper
性能 高(内存操作) 中(需要集群协调)
一致性 AP模型(可能丢锁) CP模型(强一致)
实现复杂度 简单 复杂(需处理会话)
五、锁优化与排错:从理论到实战
1. 性能优化四板斧
减少锁粒度:用ConcurrentHashMap代替
Collections.synchronizedMap
降低锁时间:把与共享资源无关的代码移出同步块
锁分离策略:读写锁分离,写时复制(CopyOnWrite)
无锁数据结构:LongAdder替代AtomicLong
2. 死锁排查工具箱
jstack:
bash
Copy Code
jstack -l
Arthas:
bash
Copy Code
thread -b # 自动检测死锁
代码规范:
按固定顺序获取锁
使用tryLock()设置超时时间
六、锁的哲学:何时不用锁?
1. 无锁编程的终极挑战
ThreadLocal:线程封闭(每个线程独立副本)
不变性模式:使用final不可变对象
Actor模型:Akka框架的消息信箱机制
2. 分布式系统新思路
CAS+版本号:ETCD的事务机制
CRDT无冲突复制数据类型:适用于最终一致性场景
七、灵魂拷问:你的锁真的安全吗?
synchronized锁String常量池:不同业务可能共享同一把锁
锁对象被修改:lock = new Object()导致锁失效
锁异常未释放:lock.lock()后代码抛异常未进finally块
《Java并发编程实战》作者Brian Goetz警告:
“并发BUG就像薛定谔的猫,你永远不知道何时会跳出来挠你”
本文配套速查表:
锁选择决策树:
单机低竞争 → synchronized
需要超时/公平锁 → ReentrantLock
读多写少 → ReadWriteLock
跨JVM → Redisson/Zookeeper
死锁预防口诀:
“一锁一线程,顺序要固定,超时不能忘,监控必须上”
( 警告:未经严格测试的锁策略可能导致线上血案,请谨慎操作)