在并发编程的世界里,锁就像交通信号灯,协调着多个线程对共享资源的有序访问。记得我第一次处理高并发订单系统时,因为锁使用不当导致整个系统吞吐量暴跌,那次惨痛教训让我深刻理解了锁机制的重要性。
锁的本质是解决并发环境下资源竞争的同步机制。当多个线程同时访问共享数据时,如果没有适当的锁控制,就会出现数据不一致的问题。比如两个线程同时读取账户余额为100元,都进行+100操作后写入,理想结果应该是300元,但实际可能只有200元。
悲观锁的工作逻辑就像古代城门守卫——"宁可错杀一千,不可放过一个"。它假设并发冲突必然发生,所以在操作数据前就先加锁,确保自己在操作期间独占资源。
典型实现方式:
SELECT ... FOR UPDATE语句synchronized关键字和ReentrantLock类java复制// Java悲观锁示例
public synchronized void transfer(Account from, Account to, int amount) {
if (from.getBalance() >= amount) {
from.debit(amount);
to.credit(amount);
}
}
适用场景分析:
注意事项:悲观锁最大的风险是死锁。我曾遇到过一个生产事故:线程A锁定了表1等待表2,线程B锁定了表2等待表1,导致系统完全卡死。解决方法包括设置锁超时、按固定顺序获取锁等。
乐观锁就像现代自助结账系统——先假设大家都会守规矩,出现问题再处理。它通过版本号或CAS机制,在提交操作时检查数据是否被其他线程修改过。
版本号实现原理:
sql复制-- SQL乐观锁示例
UPDATE products
SET stock = stock - 1,
version = version + 1
WHERE id = 1001
AND version = 5; -- 初始读取的版本号
CAS(Compare-And-Swap)实现:
java复制// Java原子类CAS示例
AtomicInteger counter = new AtomicInteger(0);
public void safeIncrement() {
int oldValue;
int newValue;
do {
oldValue = counter.get();
newValue = oldValue + 1;
} while (!counter.compareAndSet(oldValue, newValue));
}
适用场景分析:
实战心得:在秒杀系统中,我们最初使用悲观锁导致性能极差,改为乐观锁后QPS提升了8倍。但要注意,当冲突率超过20%时,乐观锁的重试开销会抵消其优势,此时应考虑其他方案。
局部锁就像大型停车场的分区管理——不是锁住整个停车场,而是只锁正在使用的车位。这种锁机制将资源划分为多个独立区域,每个区域有自己的锁。
ConcurrentHashMap的分段锁实现:
java复制// ConcurrentHashMap使用示例
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("key1", 1); // 只锁定相关的哈希桶
数据库行锁与表锁对比:
适用场景分析:
性能优化技巧:在我们的支付系统中,将用户账户按ID哈希分成16个分区,使并发能力提升了12倍。但要注意分区数量不是越多越好,根据服务器CPU核心数设置分区数通常是最佳实践。
自旋锁采用"忙等待"策略,线程在获取锁失败时会持续尝试,而不是立即挂起。这避免了线程上下文切换的开销,但会占用CPU资源。
适用场景:
c复制// 自旋锁伪代码示例
while (!atomic_compare_exchange(&lock, 0, 1)) {
// 自旋等待
}
读写锁将操作分为读锁和写锁,允许多个读操作并行,但写操作必须独占。这大幅提升了读密集型应用的性能。
ReentrantReadWriteLock使用示例:
java复制ReadWriteLock rwLock = new ReentrantReadWriteLock();
public void readData() {
rwLock.readLock().lock();
try {
// 读取操作
} finally {
rwLock.readLock().unlock();
}
}
public void writeData() {
rwLock.writeLock().lock();
try {
// 写入操作
} finally {
rwLock.writeLock().unlock();
}
}
在分布式系统中,传统的单机锁机制不再适用,需要引入分布式锁来协调多个节点的访问。
Redis实现方案:
java复制// 基于Redis的分布式锁
public boolean tryLock(String key, String value, long expireTime) {
return redisTemplate.opsForValue()
.setIfAbsent(key, value, expireTime, TimeUnit.MILLISECONDS);
}
public void unlock(String key, String value) {
String currentValue = redisTemplate.opsForValue().get(key);
if (value.equals(currentValue)) {
redisTemplate.delete(key);
}
}
ZooKeeper实现方案:
避坑指南:分布式锁必须处理网络分区和时钟漂移问题。我们曾因未设置合理的锁超时时间,导致一个节点崩溃后锁永远不释放。后来采用Redlock算法并设置自动续期才解决。
| 锁类型 | 吞吐量 | 延迟 | 适用场景 | 实现复杂度 |
|---|---|---|---|---|
| 悲观锁 | 低 | 高 | 强一致性需求 | 低 |
| 乐观锁 | 高(低冲突时) | 低 | 读多写少 | 中 |
| 分段锁 | 高 | 中 | 可分区资源 | 高 |
| 自旋锁 | 高(短等待) | 极低 | 极短临界区 | 低 |
| 读写锁 | 高(读多时) | 中 | 读多写少 | 中 |
是否需要跨进程/机器协调?
操作主要是读取?
冲突概率高吗?
资源可以分区吗?
死锁问题:
锁竞争激烈:
乐观锁重试过多:
在实际项目中,我曾遇到一个棘手的性能问题:系统在高峰期响应缓慢。通过性能分析发现是商品详情页的库存检查使用了悲观锁,改为乐观锁+本地缓存后,性能提升了15倍。这让我深刻认识到,没有最好的锁,只有最适合场景的锁。