1. Java并发编程中的锁机制概述
在Java开发中,锁是保障线程安全的核心机制。想象一下银行转账的场景:如果没有锁机制,两个人同时向同一个账户转账,可能会导致余额计算错误。这就是为什么我们需要锁——它确保同一时间只有一个线程能访问共享资源。
Java的锁机制经历了多次演进:
- JDK 1.0:只有基本的synchronized
- JDK 1.5:引入更灵活的ReentrantLock
- JDK 1.8:新增高性能的StampedLock
- 分布式系统:Redis/ZooKeeper分布式锁
关键点:锁不是越高级越好,而是要根据具体场景选择最合适的方案。就像选择交通工具,短途骑自行车更灵活,长途则开车更合适。
2. synchronized内置锁深度解析
2.1 基本用法与原理
synchronized是JVM级别的内置锁,它的实现基于对象头中的Mark Word。当线程进入synchronized代码块时:
- 尝试获取对象的monitor
- 成功则执行代码
- 失败则进入阻塞队列等待
java复制// 对象锁示例
public class Counter {
private int count;
private final Object lock = new Object();
public void increment() {
synchronized(lock) { // 锁定特定对象
count++;
}
}
}
2.2 类锁与对象锁的区别
很多初学者容易混淆这两种锁:
- 对象锁:锁定的是实例对象(this)
- 类锁:锁定的是Class对象(静态方法或Class对象)
java复制class LockExample {
// 类锁
public static synchronized void classLock() {
// 锁定的是LockExample.class
}
// 对象锁
public synchronized void instanceLock() {
// 锁定的是this实例
}
}
2.3 锁升级过程(偏向锁→轻量级锁→重量级锁)
现代JVM采用锁升级策略优化性能:
- 初始状态:无锁
- 第一个线程访问:偏向锁(记录线程ID)
- 出现竞争:升级为轻量级锁(CAS自旋)
- 自旋超过阈值:升级为重量级锁(操作系统互斥量)
实测数据:在低竞争场景下,偏向锁可以减少90%以上的同步开销。
3. ReentrantLock显式锁详解
3.1 核心特性对比
与synchronized相比,ReentrantLock提供了更多高级功能:
| 特性 | synchronized | ReentrantLock |
|---|---|---|
| 公平锁 | 不支持 | 支持 |
| 尝试获取锁 | 不支持 | tryLock() |
| 可中断 | 不支持 | lockInterruptibly() |
| 超时获取 | 不支持 | tryLock(timeout) |
| 条件变量 | 有限支持 | 多Condition支持 |
3.2 公平锁与非公平锁实战
公平锁按请求顺序分配,非公平锁允许插队:
java复制// 创建公平锁
ReentrantLock fairLock = new ReentrantLock(true);
// 创建非公平锁(默认)
ReentrantLock unfairLock = new ReentrantLock();
实测性能差异:
- 高并发下,非公平锁吞吐量比公平锁高30-50%
- 但公平锁能避免线程饥饿问题
3.3 条件变量(Condition)的高级用法
Condition提供了更精细的线程等待/通知机制:
java复制class BoundedBuffer {
final Lock lock = new ReentrantLock();
final Condition notFull = lock.newCondition();
final Condition notEmpty = lock.newCondition();
void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length)
notFull.await();
items[putptr] = x;
if (++putptr == items.length) putptr = 0;
++count;
notEmpty.signal();
} finally {
lock.unlock();
}
}
}
4. 读写锁与StampedLock实战
4.1 ReadWriteLock适用场景分析
读写锁适用于读多写少的场景,如:
- 缓存系统
- 配置中心
- 商品详情页
java复制class CachedData {
private final ReadWriteLock rwl = new ReentrantReadWriteLock();
Object processCachedData() {
rwl.readLock().lock();
try {
if (!valid) {
// 必须释放读锁才能获取写锁
rwl.readLock().unlock();
rwl.writeLock().lock();
try {
// 重新检查状态
if (!valid) {
data = ...
valid = true;
}
// 降级为读锁
rwl.readLock().lock();
} finally {
rwl.writeLock().unlock();
}
}
return data;
} finally {
rwl.readLock().unlock();
}
}
}
4.2 StampedLock性能优化技巧
StampedLock的三种模式:
- 写锁:独占访问
- 悲观读:类似ReadWriteLock的读锁
- 乐观读:不加锁,通过validate()检查
java复制class Point {
private double x, y;
private final StampedLock sl = new StampedLock();
double distanceFromOrigin() {
long stamp = sl.tryOptimisticRead();
double currentX = x, currentY = y;
if (!sl.validate(stamp)) {
stamp = sl.readLock();
try {
currentX = x;
currentY = y;
} finally {
sl.unlockRead(stamp);
}
}
return Math.sqrt(currentX*currentX + currentY*currentY);
}
}
性能测试:在读占比99%的场景下,StampedLock比ReadWriteLock快3-5倍。
5. 分布式锁实现方案
5.1 Redis分布式锁最佳实践
Redisson提供的分布式锁特性:
- 自动续期
- 可重入
- 看门狗机制
- 红锁算法(RedLock)
java复制@Autowired
private RedissonClient redisson;
public void processPayment(String orderId) {
RLock lock = redisson.getLock("payment:" + orderId);
try {
boolean locked = lock.tryLock(5, 30, TimeUnit.SECONDS);
if (locked) {
// 处理支付逻辑
paymentService.process(orderId);
}
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
5.2 分布式锁常见陷阱
- 死锁风险:忘记设置过期时间或未捕获异常
- 误删锁:未使用唯一value标识
- 锁续期问题:业务执行时间超过锁过期时间
- 时钟漂移:多节点时间不同步
解决方案:使用成熟的框架如Redisson,避免重复造轮子。
6. 数据库锁机制深度解析
6.1 乐观锁实现方案
乐观锁的三种实现方式:
- 版本号机制(最常用)
- 时间戳
- 条件比对
java复制// MyBatis Plus乐观锁示例
@Version
private Integer version;
public void updateWithOptimisticLock(Product product) {
int retry = 0;
while (retry < 3) {
Product current = productMapper.selectById(product.getId());
product.setVersion(current.getVersion());
int rows = productMapper.updateById(product);
if (rows > 0) return;
retry++;
}
throw new OptimisticLockException("更新失败");
}
6.2 悲观锁使用场景
适合强一致性要求的场景:
- 库存扣减
- 账户余额变更
- 订单状态修改
sql复制-- MySQL悲观锁示例
START TRANSACTION;
SELECT * FROM account WHERE id = 1 FOR UPDATE;
-- 执行更新操作
UPDATE account SET balance = balance - 100 WHERE id = 1;
COMMIT;
7. 锁性能优化实战技巧
7.1 锁粒度控制
错误示范:
java复制public synchronized void processOrder() {
// 包含网络IO等耗时操作
// ...
}
优化方案:
java复制public void processOrder() {
// 非同步代码
synchronized(this) {
// 只同步必要部分
}
// 其他非同步代码
}
7.2 避免死锁的编码规范
- 按固定顺序获取多把锁
- 设置锁超时时间
- 避免在锁内调用外部方法
- 使用工具检测死锁(如jstack)
java复制// 正确的锁顺序
public void transfer(Account from, Account to, int amount) {
Account first = from.getId() < to.getId() ? from : to;
Account second = from.getId() < to.getId() ? to : from;
synchronized(first) {
synchronized(second) {
// 转账逻辑
}
}
}
8. Spring Boot中的锁实践
8.1 声明式锁注解
使用自定义注解简化锁操作:
java复制@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RedisLock {
String key();
long expire() default 30;
TimeUnit timeUnit() default TimeUnit.SECONDS;
}
@Aspect
@Component
public class RedisLockAspect {
@Autowired
private RedissonClient redisson;
@Around("@annotation(lock)")
public Object around(ProceedingJoinPoint pjp, RedisLock lock) throws Throwable {
RLock rLock = redisson.getLock(lock.key());
try {
if (rLock.tryLock(0, lock.expire(), lock.timeUnit())) {
return pjp.proceed();
}
throw new RuntimeException("获取锁失败");
} finally {
if (rLock.isHeldByCurrentThread()) {
rLock.unlock();
}
}
}
}
8.2 分布式锁模板方法
封装通用锁模板:
java复制@Component
public class LockTemplate {
@Autowired
private RedissonClient redisson;
public <T> T executeWithLock(String lockKey, long waitTime, long leaseTime,
TimeUnit unit, Supplier<T> supplier) {
RLock lock = redisson.getLock(lockKey);
try {
if (lock.tryLock(waitTime, leaseTime, unit)) {
return supplier.get();
}
throw new RuntimeException("获取锁失败");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException(e);
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
}
9. 锁监控与问题排查
9.1 锁竞争监控方案
- JConsole:查看线程阻塞情况
- VisualVM:分析锁竞争热点
- Arthas:实时监控锁状态
bash复制# 查看锁信息 monitor -c 5 java.lang.Object lock - Prometheus + Grafana:分布式锁监控
9.2 常见死锁案例分析
案例1:同步方法调用另一个同步方法
java复制class A {
public synchronized void methodA() {
new B().methodB();
}
}
class B {
public synchronized void methodB() {
new A().methodA();
}
}
案例2:锁顺序不一致
java复制// 线程1
synchronized(a) {
synchronized(b) {}
}
// 线程2
synchronized(b) {
synchronized(a) {}
}
排查工具:
bash复制jstack <pid> | grep -A 10 deadlock
10. 锁的选择决策树
根据场景选择锁的决策流程:
-
是否跨JVM?
- 是 → 分布式锁(Redis/ZooKeeper)
- 否 → 进入2
-
读写比例如何?
- 读远大于写 → ReadWriteLock/StampedLock
- 读写均衡 → 进入3
-
需要高级特性?
- 需要(公平锁/可中断等) → ReentrantLock
- 不需要 → synchronized
-
性能要求极高?
- 是 → 考虑无锁编程(CAS)
- 否 → 保持当前选择
实际项目中,建议先用synchronized,遇到瓶颈再考虑更复杂的锁方案。我在电商系统性能优化中发现,80%的同步场景用synchronized就足够了,过度设计反而会增加复杂度。