Java锁这样用,从单机到分布式一步到位
单机锁已经不够用了?分布式系统中如何保证数据安全?今天我们来聊聊从单机锁到分布式锁的完整解决方案,最后用一个注解就能搞定所有锁的问题!
为什么需要锁?
在多线程或多进程环境中,多个操作同时访问同一资源时可能出现数据不一致的问题。锁就是用来保证同一时间只有一个操作能访问共享资源。
锁的作用:
简单理解: 就像厕所门上的锁,同一时间只能有一个人使用,其他人必须等待。
单机锁的局限性
synchronized关键字
Java最简单的锁机制。- public class CounterService {
- private int count = 0;
-
- public synchronized void increment() {
- count++;
- }
-
- public synchronized int getCount() {
- return count;
- }
- }
复制代码 ReentrantLock可重入锁
更灵活的锁机制。- public class CounterService {
- private int count = 0;
- private final ReentrantLock lock = new ReentrantLock();
-
- public void increment() {
- lock.lock();
- try {
- count++;
- } finally {
- lock.unlock();
- }
- }
- }
复制代码 单机锁的问题:
- 只能在单个JVM内生效
- 多个服务实例之间无法互斥
- 分布式环境下失效
分布式环境的挑战
当应用部署在多台服务器上时,单机锁就不够用了。
分布式环境下的问题:
- 多个服务实例可能同时执行相同操作
- 库存扣减、订单生成等场景容易出现数据不一致
- 需要跨JVM的锁机制
基于Redis的分布式锁
简单的Redis分布式锁
使用Redis的SET命令实现。- @Component
- public class SimpleRedisLock {
-
- @Autowired
- private StringRedisTemplate redisTemplate;
-
- public boolean tryLock(String key, String value, long expireTime) {
- Boolean result = redisTemplate.opsForValue()
- .setIfAbsent(key, value, expireTime, TimeUnit.SECONDS);
- return Boolean.TRUE.equals(result);
- }
-
- public void releaseLock(String key, String value) {
- String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
- "return redis.call('del', KEYS[1]) else return 0 end";
- redisTemplate.execute(new DefaultRedisScript<>(script, Long.class),
- Arrays.asList(key), value);
- }
- }
复制代码 使用示例
- @Service
- public class OrderService {
-
- @Autowired
- private SimpleRedisLock redisLock;
-
- public void createOrder(Long userId) {
- String lockKey = "order:user:" + userId;
- String lockValue = UUID.randomUUID().toString();
-
- if (redisLock.tryLock(lockKey, lockValue, 30)) {
- try {
- // 执行订单创建逻辑
- doCreateOrder(userId);
- } finally {
- redisLock.releaseLock(lockKey, lockValue);
- }
- } else {
- throw new RuntimeException("获取锁失败,请稍后重试");
- }
- }
-
- private void doCreateOrder(Long userId) {
- // 具体的订单创建逻辑
- }
- }
复制代码 基于Redisson的分布式锁
Redisson提供了更完善的分布式锁实现。
引入依赖
- <dependency>
- <groupId>org.redisson</groupId>
- redisson-spring-boot-starter</artifactId>
- <version>3.20.1</version>
- </dependency>
复制代码 配置Redisson
- @Configuration
- public class RedissonConfig {
-
- @Bean
- public RedissonClient redissonClient() {
- Config config = new Config();
- config.useSingleServer()
- .setAddress("redis://localhost:6379")
- .setDatabase(0);
- return Redisson.create(config);
- }
- }
复制代码 使用Redisson锁
- @Service
- public class OrderService {
-
- @Autowired
- private RedissonClient redissonClient;
-
- public void createOrder(Long userId) {
- String lockKey = "order:user:" + userId;
- RLock lock = redissonClient.getLock(lockKey);
-
- try {
- if (lock.tryLock(10, 30, TimeUnit.SECONDS)) {
- // 执行订单创建逻辑
- doCreateOrder(userId);
- } else {
- throw new RuntimeException("获取锁失败,请稍后重试");
- }
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- throw new RuntimeException("获取锁被中断");
- } finally {
- if (lock.isHeldByCurrentThread()) {
- lock.unlock();
- }
- }
- }
- }
复制代码 Redisson的优势:
- 自动续期机制
- 可重入锁支持
- 公平锁、读写锁等多种锁类型
- 异常处理更完善
注解式分布式锁工具
手动加锁解锁容易出错,我们可以通过注解来简化使用。
自定义锁注解
- @Target(ElementType.METHOD)
- @Retention(RetentionPolicy.RUNTIME)
- public @interface DistributedLock {
-
- String key() default "";
-
- long waitTime() default 10;
-
- long leaseTime() default 30;
-
- TimeUnit timeUnit() default TimeUnit.SECONDS;
-
- String errorMessage() default "获取锁失败,请稍后重试";
- }
复制代码 AOP切面实现
- @Aspect
- @Component
- public class DistributedLockAspect {
-
- @Autowired
- private RedissonClient redissonClient;
-
- @Around("@annotation(distributedLock)")
- public Object around(ProceedingJoinPoint joinPoint, DistributedLock distributedLock) throws Throwable {
- String lockKey = generateLockKey(joinPoint, distributedLock.key());
- RLock lock = redissonClient.getLock(lockKey);
-
- try {
- boolean acquired = lock.tryLock(
- distributedLock.waitTime(),
- distributedLock.leaseTime(),
- distributedLock.timeUnit()
- );
-
- if (!acquired) {
- throw new RuntimeException(distributedLock.errorMessage());
- }
-
- return joinPoint.proceed();
-
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- throw new RuntimeException("获取锁被中断");
- } finally {
- if (lock.isHeldByCurrentThread()) {
- lock.unlock();
- }
- }
- }
-
- private String generateLockKey(ProceedingJoinPoint joinPoint, String key) {
- if (StringUtils.hasText(key)) {
- return parseKey(key, joinPoint);
- }
-
- String className = joinPoint.getTarget().getClass().getSimpleName();
- String methodName = joinPoint.getSignature().getName();
- return className + ":" + methodName;
- }
-
- private String parseKey(String key, ProceedingJoinPoint joinPoint) {
- if (key.contains("#")) {
- // 支持SpEL表达式解析参数
- return parseSpEL(key, joinPoint);
- }
- return key;
- }
-
- private String parseSpEL(String key, ProceedingJoinPoint joinPoint) {
- // SpEL表达式解析实现
- // 这里简化处理,实际项目中可以使用Spring的SpEL解析器
- return key.replace("#userId", String.valueOf(joinPoint.getArgs()[0]));
- }
- }
复制代码 使用注解式分布式锁
- @Service
- public class OrderService {
-
- @DistributedLock(key = "order:user:#userId", waitTime = 5, leaseTime = 30)
- public void createOrder(Long userId) {
- // 方法执行时自动加锁
- doCreateOrder(userId);
- // 方法执行完成后自动释放锁
- }
-
- @DistributedLock(key = "inventory:product:#productId")
- public void decreaseInventory(Long productId, Integer quantity) {
- // 库存扣减逻辑
- doDecreaseInventory(productId, quantity);
- }
-
- private void doCreateOrder(Long userId) {
- // 具体的订单创建逻辑
- }
-
- private void doDecreaseInventory(Long productId, Integer quantity) {
- // 具体的库存扣减逻辑
- }
- }
复制代码 分布式锁的注意事项
1. 锁超时时间设置
锁的超时时间要根据业务执行时间合理设置。- // 根据业务复杂度设置合适的超时时间
- @DistributedLock(key = "complex:task:#taskId", leaseTime = 60) // 复杂任务60秒
- public void executeComplexTask(String taskId) {
- // 复杂业务逻辑
- }
- @DistributedLock(key = "simple:task:#taskId", leaseTime = 10) // 简单任务10秒
- public void executeSimpleTask(String taskId) {
- // 简单业务逻辑
- }
复制代码 2. 锁的粒度控制
锁的粒度要合适,既要保证安全性,又要避免性能问题。- // 细粒度锁 - 针对具体用户
- @DistributedLock(key = "user:operation:#userId")
- public void userOperation(Long userId) {
- // 只锁定特定用户的操作
- }
- // 粗粒度锁 - 全局锁(慎用)
- @DistributedLock(key = "global:operation")
- public void globalOperation() {
- // 全局操作,会影响所有用户
- }
复制代码 3. 异常处理
确保在异常情况下锁能正确释放。- @DistributedLock(key = "order:#orderId", errorMessage = "订单正在处理中,请勿重复操作")
- public void processOrder(Long orderId) {
- try {
- // 业务逻辑
- doProcessOrder(orderId);
- } catch (Exception e) {
- // 记录日志
- log.error("订单处理失败: {}", orderId, e);
- throw e; // 重新抛出异常,确保事务回滚
- }
- // 锁会在方法结束时自动释放
- }
复制代码 性能优化建议
1. 连接池配置
- @Configuration
- public class RedissonConfig {
-
- @Bean
- public RedissonClient redissonClient() {
- Config config = new Config();
- config.useSingleServer()
- .setAddress("redis://localhost:6379")
- .setConnectionPoolSize(50) // 连接池大小
- .setConnectionMinimumIdleSize(10); // 最小空闲连接
- return Redisson.create(config);
- }
- }
复制代码 2. 锁等待策略
- // 快速失败策略
- @DistributedLock(key = "quick:#id", waitTime = 0)
- public void quickOperation(String id) {
- // 不等待,立即返回
- }
- // 适度等待策略
- @DistributedLock(key = "normal:#id", waitTime = 3)
- public void normalOperation(String id) {
- // 等待3秒
- }
复制代码 总结
Java锁的演进过程:
单机锁:
- synchronized、ReentrantLock
- 只能在单个JVM内使用
分布式锁:
注解式分布式锁:
- 使用简单,一个注解搞定
- 减少重复代码,降低出错概率
选择建议:
- 单机应用:使用synchronized或ReentrantLock
- 分布式应用:使用Redisson分布式锁
- 追求简洁:使用注解式分布式锁
掌握这套锁的升级方案,让你的应用在任何环境下都能保证数据安全!
如果这篇文章对你有帮助,请不要忘记:
<ul>
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |