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>
<artifactId>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内使用

分布式锁:

  • 基于Redis实现
  • 支持跨JVM协调

注解式分布式锁:

  • 使用简单,一个注解搞定
  • 减少重复代码,降低出错概率

选择建议:

  • 单机应用:使用synchronized或ReentrantLock
  • 分布式应用:使用Redisson分布式锁
  • 追求简洁:使用注解式分布式锁

掌握这套锁的升级方案,让你的应用在任何环境下都能保证数据安全!


如果这篇文章对你有帮助,请不要忘记:

  • 点赞支持一下
  • 关注我,获取更多Java技术干货
  • 推荐给你的朋友同事

关注微信公众号【划水的程序猿】,专注于Java技术分享。让我们一起在技术的海洋中成长!

Java锁这样用,从单机到分布式一步到位的更多相关文章

  1. java就业指南 zookeeper分布式系统 zookeeper实现分布式锁 有用

    目前几乎很多大型网站及应用都是分布式部署的,分布式场景中的数据一致性问题一直是一个比较重要的话题.分布式的CAP理论告诉我们“任何一个 分布式系统都无法同时满足一致性(Consistency).可用性 ...

  2. 分布式锁(2) ----- 基于redis的分布式锁

    分布式锁系列文章 分布式锁(1) ----- 介绍和基于数据库的分布式锁 分布式锁(2) ----- 基于redis的分布式锁 分布式锁(3) ----- 基于zookeeper的分布式锁 代码:ht ...

  3. 转载:Hadoop安装教程_单机/伪分布式配置_Hadoop2.6.0/Ubuntu14.04

    原文 http://www.powerxing.com/install-hadoop/ 当开始着手实践 Hadoop 时,安装 Hadoop 往往会成为新手的一道门槛.尽管安装其实很简单,书上有写到, ...

  4. Hadoop安装教程_单机/伪分布式配置_Hadoop2.6.0/Ubuntu14.04

    摘自: http://www.cnblogs.com/kinglau/p/3796164.html http://www.powerxing.com/install-hadoop/ 当开始着手实践 H ...

  5. hbase 0.96 单机伪分布式配置文件及遇到的问题 find命令

    http://www.apache.org/dyn/closer.cgi/hbase/ 国外的站点下载速度慢,可以考虑国内的镜像网站~ 前面已经部署好了hadoop2.2.0单机伪分布式.必须先安装h ...

  6. [大数据]-Elasticsearch5.3.1+Kibana5.3.1从单机到分布式的安装与使用<1>

    一.Elasticsearch,Kibana简介: Elasticsearch是一个基于Apache Lucene(TM)的开源搜索引擎.无论在开源还是专有领域, Lucene可以被认为是迄今为止最先 ...

  7. Hadoop:Hadoop单机伪分布式的安装和配置

    http://blog.csdn.net/pipisorry/article/details/51623195 因为lz的linux系统已经安装好了很多开发环境,可能下面的步骤有遗漏. 之前是在doc ...

  8. 【HADOOP】| 环境搭建:从零开始搭建hadoop大数据平台(单机/伪分布式)-下

    因篇幅过长,故分为两节,上节主要说明hadoop运行环境和必须的基础软件,包括VMware虚拟机软件的说明安装.Xmanager5管理软件以及CentOS操作系统的安装和基本网络配置.具体请参看: [ ...

  9. Hadoop安装教程_单机/伪分布式配置_Hadoop2.6.0/Ubuntu14.04(转)

    http://www.powerxing.com/install-hadoop/ http://blog.csdn.net/beginner_lee/article/details/6429146 h ...

  10. Hadoop安装教程_单机/伪分布式配置_CentOS6.4/Hadoop2.6.0

    Hadoop安装教程_单机/伪分布式配置_CentOS6.4/Hadoop2.6.0 环境 本教程使用 CentOS 6.4 32位 作为系统环境,请自行安装系统.如果用的是 Ubuntu 系统,请查 ...

随机推荐

  1. JVM 新生代垃圾回收如何避免全堆扫描?

    JVM 新生代垃圾回收如何避免全堆扫描? 在 JVM 新生代的垃圾回收(Minor GC)过程中,为了提高效率并减少回收时间,垃圾收集器会避免对整个堆(包括新生代和老年代)进行扫描.以下是 JVM 如 ...

  2. 2319. 判断矩阵是否是一个 X 矩阵

    2319. 判断矩阵是否是一个 X 矩阵 题解: 模拟 class Solution { public boolean checkXMatrix(int[][] grid) { int n = gri ...

  3. 海康摄像头SDK在Linux、windows下的兼容问题(二)已解决

    上一篇提出的问题,在前几天解决了. 海康的技术人员给出了指导,在Linux库加载失败的时候,需要在代码中手动指定配置文件. 库文件加载说明] // 1. lib文件夹里面所有库文件libhcnetsd ...

  4. ElasticSearch学习文档

    中文文档:https://doc.codingdict.com/elasticsearch/ Elastic 官方网站:Free and Open Search: The Creators of El ...

  5. 【神兵利器】Windows平台shellcode免杀加载器

    项目介绍 免杀,bypassav,免杀框架,nim,shellcode,使用nim编写的shellcode加载器,可快速生成免杀可执行文件 下载地址 Windows平台shellcode免杀加载器下载 ...

  6. 【记录】IDA|Ollydbg|两种软件中查看指令在原二进制文件中的位置,及查看原二进制文件位置对应的反汇编指令的方式

    文章目录 在IDA中查看指令地址 在Ollydbg中查看指令地址 在Ollydbg中查看地址对应的指令 在IDA中查看指令地址 在Ollydbg中查看指令地址 ollydbg在对应指令处,右键-查看- ...

  7. 马尔柯夫的"概率转换和市场份额和概率矩阵"

    案例一 案例二 分析转移概率矩阵求法 分析求9月份各厂家分别拥有的市场份额  案例3 案例4 分析先算第二年,再根据第二年算第三年 问题2的解法:根据第二年市场份额算第三年的市场份额 案例5:确定平衡 ...

  8. 稀疏贝叶斯谱估计及EM算法求解

    稀疏贝叶斯 稀疏贝叶斯学习(sparse bayes learning,SBL)最早被提出是作为一种机器学习算法[1].但是在这里我们主要用它来做谱估计,作为求解稀疏重构问题的方法[2].稀疏重构还有 ...

  9. 安装Scikit-learn 0.24.2版本

    安装Scikit-learn 0.24.2版本 Anaconda 安装scikit-learn是0.19版本,目前最新版本是0.24.2,之前的版本与现在的版本之间差距还是比较大,因此有必要对scik ...

  10. codeup之查找

    Description 输入数组长度 n 输入数组 a[1-n] 输入查找个数m 输入查找数字b[1-m] 输出 YES or NO 查找有则YES 否则NO . Input 输入有多组数据. 每组输 ...