Java 中普通锁与分布式锁的区别

目录

1. 概述

2. 普通锁

2.1 什么是普通锁

2.2 普通锁的作用范围

3. 分布式锁

3.1 什么是分布式锁

3.2 分布式锁的常见实现方式

4. 普通锁与分布式锁的核心区别

5. 示例说明:库存扣减

5.1 单机部署场景

5.2 集群部署场景

6. 常见应用场景

6.1 普通锁适用场景

6.2 分布式锁适用场景

7. 常见误区

7.1 误区一:用了 synchronized 就一定线程安全

7.2 误区二:分布式锁可以完全替代普通锁

7.3 误区三:只要加了分布式锁就一定安全

8. 如何选择

9. 总结


1. 概述

在 Java 并发编程中,普通锁分布式锁都是为了解决“多个执行单元同时操作共享资源”所带来的并发安全问题。

它们的核心区别在于:

普通锁解决的是单个 JVM 内部的多线程并发问题;分布式锁解决的是多个 JVM、多个服务实例、甚至多台机器之间的并发问题。


2. 普通锁

2.1 什么是普通锁

普通锁通常指 Java 本地锁,也可以理解为单机锁。它只在当前 Java 程序所在的 JVM 内部生效。

常见的普通锁包括:

synchronized
ReentrantLock
ReadWriteLock
StampedLock

2.2 普通锁的作用范围

普通锁只能控制当前 JVM 进程内部的线程并发。

例如:

synchronized (this) {
    // 执行业务逻辑
}

这段代码可以保证在同一个 JVM 中,同一时间只有一个线程进入该代码块。

但是,如果系统部署了多个服务实例,每个实例都有自己的 JVM,那么每个 JVM 中的锁都是独立的,彼此之间无法感知。


3. 分布式锁

3.1 什么是分布式锁

分布式锁是用于分布式系统中的一种锁机制,主要用于控制多个服务实例、多个 JVM 或多台机器之间对共享资源的并发访问。

在微服务或集群部署场景下,同一个业务服务可能会启动多个实例,例如:

用户请求
   ↓
负载均衡
   ↓
服务实例 A / 服务实例 B / 服务实例 C

此时,多个服务实例都有可能同时执行同一段业务逻辑。如果只使用 Java 普通锁,只能锁住某一个服务实例内部的线程,无法控制其他实例。

因此,需要使用分布式锁。

3.2 分布式锁的常见实现方式

分布式锁通常依赖一个所有服务实例都能访问的公共组件来实现,例如:

Redis
Zookeeper
Etcd
数据库

其中,Redis 分布式锁在实际开发中使用较多。


4. 普通锁与分布式锁的核心区别

对比项

普通锁

分布式锁

作用范围

单个 JVM 内部

多个 JVM、多个服务实例、多台机器

解决问题

单机多线程并发

分布式环境下的并发

常见实现

synchronized、ReentrantLock、ReadWriteLock

Redis、Zookeeper、Etcd、数据库

是否跨服务生效

性能

较高,无网络通信

相对较低,需要网络通信

实现复杂度

较低

较高

典型场景

单体应用、单实例服务

微服务、集群部署、分布式系统

需要考虑的问题

死锁、锁释放、锁粒度

超时、续期、误删锁、网络异常、锁释放、可重入性


5. 示例说明:库存扣减

5.1 单机部署场景

如果一个商城系统只部署了一个服务实例,那么可以使用普通锁来保证库存扣减的线程安全。

synchronized (this) {
    reduceStock();
}

在这种情况下,多个线程同时请求扣减库存时,普通锁可以保证同一时间只有一个线程执行扣减操作。


5.2 集群部署场景

如果商城系统部署了多个服务实例:

服务实例 A
服务实例 B
服务实例 C

每个服务实例都有自己的 JVM,每个 JVM 中的 synchronized 锁也都是独立的。

也就是说:

服务 A 的 synchronized 只能锁住服务 A 内部的线程
服务 B 的 synchronized 只能锁住服务 B 内部的线程
服务 C 的 synchronized 只能锁住服务 C 内部的线程

它们之间无法互相限制。

因此,服务 A、服务 B、服务 C 仍然可能同时执行扣库存操作,从而导致库存超卖等问题。

此时应使用分布式锁,例如 Redis 分布式锁:

// 获取 Redis 分布式锁
boolean locked = tryLock("stock:product:1001");

if (locked) {
    try {
        reduceStock();
    } finally {
        // 释放分布式锁
        unlock("stock:product:1001");
    }
}

这样,所有服务实例都会竞争 Redis 中的同一把锁,只有获取到锁的实例才能执行库存扣减逻辑。


6. 常见应用场景

6.1 普通锁适用场景

普通锁适用于单机环境下的线程安全控制,例如:

单体应用中的共享变量操作
单个 JVM 内部的缓存更新
单实例服务中的资源竞争
本地队列消费控制

只要系统没有多实例部署,普通锁通常就可以满足需求。


6.2 分布式锁适用场景

分布式锁适用于多个服务实例同时竞争同一资源的场景,例如:

秒杀扣库存
优惠券领取
防止重复下单
支付回调幂等处理
定时任务防止重复执行
库存同步
分布式任务调度
缓存重建防止击穿

这些场景中,多个服务实例可能同时执行相同业务逻辑,因此需要分布式锁保证全局互斥。


7. 常见误区

7.1 误区一:用了 synchronized 就一定线程安全

synchronized 只能保证当前 JVM 内部的线程安全。

如果系统是单实例部署,它可以有效防止并发问题。

如果系统是多实例部署,它无法保证多个服务实例之间的并发安全。


7.2 误区二:分布式锁可以完全替代普通锁

分布式锁并不是普通锁的完全替代品。

普通锁性能更高,使用更简单,适合单机并发控制。

分布式锁需要依赖 Redis、Zookeeper 等外部组件,会带来网络通信成本和实现复杂度。

因此:

能用普通锁解决的问题,不一定要上分布式锁。
只有在多实例、多 JVM、多机器场景下,才需要考虑分布式锁。

7.3 误区三:只要加了分布式锁就一定安全

分布式锁本身也需要正确使用,否则仍然可能出问题。

使用分布式锁时需要注意:

锁要设置过期时间,防止死锁
释放锁时要判断锁是不是自己的,防止误删别人的锁
业务执行时间过长时,要考虑锁续期
加锁和设置过期时间要保证原子性
释放锁最好使用 Lua 脚本保证原子性

8. 如何选择

可以按照以下规则判断:

如果项目是单体应用,或者只部署一个服务实例:
使用普通锁即可

如果项目是微服务、集群部署,或者同一服务启动多个实例:
使用分布式锁

如果资源只在当前 JVM 内部共享:
使用普通锁

如果资源被多个服务实例共同访问:
使用分布式锁

9. 总结

普通锁和分布式锁都是为了解决并发问题,但它们的适用范围不同。

普通锁关注的是单个 JVM 内部的线程并发,例如 synchronizedReentrantLock

分布式锁关注的是多个 JVM、多个服务实例、多台机器之间的并发,常见实现方式包括 Redis、Zookeeper、Etcd 和数据库。

一句话总结:

普通锁解决单机多线程并发问题,分布式锁解决分布式环境下多服务实例之间的并发问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值