1. 引言
在多线程编程和分布式系统设计中,锁是保证数据一致性和线程安全的重要机制。本文将深入探讨从单体式应用到分布式系统中锁的实现与演进。
2. 单体式项目中的锁
2.1 synchronized关键字
在单体式项目中,最简单的加锁方式是使用synchronized关键字。
加在方法上的synchronized(不推荐)
public synchronized void process() {// 业务逻辑
}
这种方式会锁定整个方法,导致性能较差,因为同一时间只能有一个线程访问该方法。
同步代码块(推荐)
public void process() {synchronized(lockObject) {// 业务逻辑}
}
这种方式只锁定特定的代码块,性能更好,但需要确保锁定的是同一个对象。
2.2 锁对象的注意事项
Integer类型的陷阱
// 错误示例
Integer id;
synchronized(id) {
}// 正确方式
synchronized(id.toString().intern()) {
}
// 原因:Integer类型只有在保留字-127-128这个范围里,值相对的对象是判定为同一个对象的
// 如果值超过了这一个范围,视为每一个对象都是新对象,锁就会失效
// 为了解决这个问题,尝试通过toString()方法将它转换成字符类型
// 但是通过toString()方法创建的字符类型对象也是每个都是新的对象
// 因此需要使用intern()方法,使得只要值相等的对象都看作是同一个对象
2.3 ReentrantLock
除了synchronized,Java还提供了更灵活的ReentrantLock:
ReentrantLock lock = new ReentrantLock(); // 非公平锁
ReentrantLock fairLock = new ReentrantLock(true); // 公平锁public void process() {lock.lock();try {// 业务逻辑} finally {lock.unlock();}
}
3. 分布式环境下的锁挑战
在分布式系统中,单体式的锁机制失效了,因为每个服务节点都有自己的JVM,锁只能控制当前节点。
3.1 基于Redis的分布式锁
简单的setnx实现
SETNX lock_key unique_value
但这种实现存在多个问题:
- 锁无法自动释放
- 过期时间设置困难
- 存在死锁风险
4. Redisson分布式锁解决方案
4.1 基本使用
RLock lock = redisson.getLock(id);
lock.lock();
try {// 业务逻辑
} finally {lock.unlock();
}
4.2 看门狗机制
Redisson的看门狗机制会自动续期锁的过期时间:
- 默认过期时间30秒
- 看门狗每10秒检查一次,如果业务未完成则自动续期
- 避免业务执行时间超过锁过期时间导致的问题
4.3 红锁(RedLock)
在Redis集群环境下,红锁算法确保分布式锁的可靠性:
RedissonRedLock lock = new RedissonRedLock(lock1, lock2, lock3);
lock.lock();
try {// 业务逻辑
} finally {lock.unlock();
}
红锁要求大多数(N/2+1)Redis节点成功获取锁才算真正获取到锁。
4.4 实现原理
Redisson通过LUA脚本保证原子性操作:
if redis.call('exists', KEYS[1]) == 0 then redis.call('hset', KEYS[1], ARGV[2], 1);redis.call('pexpire', KEYS[1], ARGV[1]);return nil;
end;
5. 总结
从单体式锁到分布式锁的演进体现了系统架构的发展需求:
- 单体项目:synchronized和ReentrantLock足够应对
- 分布式系统:需要基于Redis等中间件实现分布式锁
- Redisson提供了生产级别的分布式锁解决方案
- 看门狗机制和红锁算法进一步增强了可靠性和可用性
选择合适的锁机制需要根据具体的业务场景和系统架构来决定,在保证数据一致性的同时,也要考虑性能和可用性的平衡。