场景
若依前后端分离版手把手教你本地搭建环境并运行项目:
https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/108465662
在上面搭建起来前后端分离的项目,如果在某些业务场景下比如抢票、秒杀时会有多线程、多定位任务、多服务节点
对同一个redis中的key进行获取、更改和存储的操作。
如果每次进行操作时不进行加锁处理,就会导致数据不准确(多卖、少卖)的情况。
注:
博客:
https://blog.csdn.net/badao_liumang_qizhi
关注公众号
霸道的程序猿
获取编程相关电子书、教程推送与免费下载。
实现
1、Redisson
Redisson – Redis Java client
with features of an in-memory data grid。
Redisson – Redis Java 客户端
具有内存数据网格的特征。
官方文档地址:
https://github.com/redisson/redisson/wiki/%E7%9B%AE%E5%BD%95
gitHub地址:
https://github.com/redisson/redisson
2、参考官方文档快速开始
引入maven依赖
<dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.16.8</version> </dependency>
新建Redisson配置类,配置redis的连接地址等信息
package com.ruoyi.quartz.config; import org.redisson.Redisson; import org.redisson.api.RedissonClient; import org.redisson.config.Config; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class RedissonConfiguration { @Value("${spring.redis.host}") private String host; @Value("${spring.redis.port}") private String port; @Value("${spring.redis.password}") private String password; @Bean public RedissonClient redissonClient() { Config config = new Config(); String url = "redis://" + host + ":" + port; config.useSingleServer().setAddress(url).setPassword(password).setDatabase(0); return Redisson.create(config); } }
这里的redis的相关信息已经在配置文件中进行配置
3、模拟一个多线程的任务,同时对redis中存储的num这个key进行操作,这个key代表票或者商品的总数量。
如果不加锁,任由所有任务都去获取同一个key,进行减1操作并存储回去,代码实现
ExecutorService executorService = Executors.newFixedThreadPool(50); //存储每个线程的返回结果 ArrayList<Future<Integer>> futureArrayList = new ArrayList<>(); //模拟50个任务发起购票/秒杀商品操作,每个任务买一个 for (int i = 0; i < 50; i++) { Future<Integer> submit = executorService.submit(() -> { int count = 0; int num = redisCache.getCacheObject("num"); num--; redisCache.setCacheObject("num", num); count++; return count; }); futureArrayList.add(submit); } Integer saleCount = 0; for (int i = 0; i < futureArrayList.size(); i++) { Future<Integer> integerFuture = futureArrayList.get(i); saleCount = saleCount + integerFuture.get(); } System.out.println("累计卖出:"+saleCount);
关于多线程的使用方式可以参考下文
Java中ExecutorService线程池的使用(Runnable和Callable多线程实现):
https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/126242904
这种不加任何锁的实现方式,导致的结果是
累计计数卖出的是50,实际上redis中减少的数量却不是
4、所有为了保持一致性,需要给每次操作同一个key之前添加锁
获取一把锁,只要锁的名字一样,就是一把锁
RLock numLock = redissonClient.getLock("numLock");
每个线程操作redis之前加锁
if(numLock.tryLock(1,1,TimeUnit.SECONDS)){ int num = redisCache.getCacheObject("num"); num--; redisCache.setCacheObject("num", num); count++; //isHeldByCurrentThread()的作用是查询当前线程是否保持此锁定 if(numLock.isHeldByCurrentThread()){ numLock.unlock(); } }
这里的tryLock的参数为最大等待时间为1秒,上锁1秒后自动解锁。
然后isHeldByCurrentThread的作用是查询当前线程是否保持此锁定。
当对redis操作结束之后,如果还保持此锁定,则调用unlock进行解锁。
完整示例代码
package com.ruoyi.quartz.task; import com.ruoyi.common.core.redis.RedisCache; import org.redisson.api.RLock; import org.redisson.api.RedissonClient; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.ArrayList; import java.util.concurrent.*; @Component("redissonDemoTask") public class RedissonDemoTask { @Autowired private RedisCache redisCache; @Autowired RedissonClient redissonClient; public void PlatformOne() throws ExecutionException, InterruptedException { //加锁的实现方式 ExecutorService executorService = Executors.newFixedThreadPool(50); ArrayList<Future<Integer>> futureArrayList = new ArrayList<>(); RLock numLock = redissonClient.getLock("numLock"); for (int i = 0; i < 50; i++) { Future<Integer> submit = executorService.submit(() -> { int count = 0; if(numLock.tryLock(1,1,TimeUnit.SECONDS)){ int num = redisCache.getCacheObject("num"); num--; redisCache.setCacheObject("num", num); count++; //isHeldByCurrentThread()的作用是查询当前线程是否保持此锁定 if(numLock.isHeldByCurrentThread()){ numLock.unlock(); } } return count; }); futureArrayList.add(submit); } Integer saleCount = 0; for (int i = 0; i < futureArrayList.size(); i++) { Future<Integer> integerFuture = futureArrayList.get(i); saleCount = saleCount + integerFuture.get(); } System.out.println("累计卖出:"+saleCount); } }
加锁效果
原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/279807.html