在高并发架构的Java应用开发中,锁是保障线程安全与数据一致性的核心组件,不同锁类型的性能开销和适用场景差异极大,选对锁能大幅提升系统吞吐量,选错则可能引发严重的性能问题。

Java内置锁的升级机制与适用场景
Java中的synchronized关键字对应的锁会随着竞争情况自动升级,从低开销的偏向锁逐步升级到高开销的重量级锁,不同阶段的锁适配不同的并发场景。
偏向锁
偏向锁是锁升级的第一个阶段,核心思想是如果一段同步代码一直被同一个线程访问,那么就不需要做同步操作,减少无竞争情况下的锁开销。
适用场景:单线程反复进入同步块的场景,比如单线程执行的本地缓存更新、单线程处理的定时任务等。当没有其他线程竞争时,偏向锁几乎不会产生额外性能损耗。
可以通过JVM参数-XX:+UseBiasedLocking开启偏向锁,JDK 15之后默认关闭偏向锁,因为现代应用中偏向锁的增益场景越来越少。
轻量级锁
当偏向锁遇到其他线程尝试竞争时,会升级为轻量级锁。轻量级锁通过CAS(Compare And Swap)操作尝试获取锁,避免线程直接进入阻塞状态,减少用户态到内核态的切换开销。
适用场景:同步块执行速度很快,且线程竞争不激烈的场景,比如短耗时的计数器累加、简单的状态标记更新等。此时线程冲突概率低,CAS操作的成功率很高,性能优于重量级锁。
public class LightweightLockDemo {
private int count = 0;
// 轻量级锁适用场景:短耗时同步操作,竞争不激烈
public void increment() {
synchronized (this) {
count++; // 同步块执行速度极快,适合轻量级锁
}
}
}
重量级锁
当轻量级锁的CAS操作多次失败,说明线程竞争非常激烈,锁会升级为重量级锁。重量级锁依赖操作系统的互斥量实现,线程获取不到锁时会进入阻塞状态,等待被唤醒。
适用场景:同步块执行耗时较长,或者线程竞争非常激烈的场景,比如涉及IO操作的同步代码、大批量数据处理的同步逻辑等。此时CAS操作的成功率极低,直接使用重量级锁避免无效的CAS自旋消耗CPU资源。
分布式场景下的分布式锁选择
当应用采用多节点部署,单机JVM内的锁无法保障跨节点的数据一致性,此时需要使用分布式锁。常见的分布式锁实现方案有基于Redis、ZooKeeper、数据库三种,不同方案适配不同的业务场景。
基于Redis的分布式锁
Redis分布式锁实现简单,性能极高,适合对并发量要求高、锁持有时间较短的场景,比如秒杀库存扣减、分布式任务调度等。
实现时需要注意设置过期时间避免死锁,同时要保证加锁和设置过期时间的原子性,推荐使用SET key value NX EX timeout命令。
import redis.clients.jedis.Jedis;
public class RedisDistributedLock {
private Jedis jedis;
private String lockKey;
private String requestId; // 唯一标识,防止其他线程误释放锁
private int expireTime; // 锁过期时间,单位秒
public RedisDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {
this.jedis = jedis;
this.lockKey = lockKey;
this.requestId = requestId;
this.expireTime = expireTime;
}
// 加锁,原子操作
public boolean lock() {
String result = jedis.set(lockKey, requestId, "NX", "EX", expireTime);
return "OK".equals(result);
}
// 释放锁,先判断是自己的锁再删除,使用Lua脚本保证原子性
public boolean unlock() {
String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Object result = jedis.eval(luaScript, 1, lockKey, requestId);
return "1".equals(result.toString());
}
}
基于ZooKeeper的分布式锁
ZooKeeper分布式锁基于临时顺序节点实现,可靠性极高,支持锁的等待队列,适合对数据一致性要求极高、锁持有时间较长的场景,比如分布式事务中的资源锁定、核心配置的统一更新等。
缺点是性能低于Redis分布式锁,因为每次操作都需要与ZooKeeper集群交互,网络开销更大。
基于数据库的分布式锁
基于数据库的实现方式最简单,通过唯一索引或者行锁实现,适合并发量极低、对性能要求不高的场景,比如低频的定时任务去重、小流量的后台管理操作等。
缺点是性能差,且数据库故障会直接导致锁服务不可用,一般不推荐在高并发场景使用。
不同场景下的锁选择策略总结
可以按照以下维度匹配对应的锁方案:
- 单线程反复进入同步块:选择偏向锁(JDK 15之前默认开启,之后需手动配置)
- 单JVM内,同步块短耗时、竞争不激烈:选择轻量级锁,直接使用
synchronized即可,JVM会自动升级 - 单JVM内,同步块长耗时、竞争激烈:选择重量级锁,同样使用
synchronized,JVM会自动升级到对应阶段 - 多节点部署,高并发、短耗时锁场景:选择Redis分布式锁
- 多节点部署,高一致性、长耗时锁场景:选择ZooKeeper分布式锁
- 多节点部署,极低并发场景:可选择数据库分布式锁
实际开发中还需要结合压测结果调整锁策略,避免过度设计,比如低并发场景下不需要引入复杂的分布式锁,单机锁就能满足需求。