若依前后端分离版手把手教你本地搭建环境并运行项目:
若依前后端分离版手把手教你本地搭建环境并运行项目_霸道流氓气质的博客-CSDN博客_前后端分离项目本地运行
在上面搭建起来前后端分离的项目,如果在某些业务场景下比如抢票、秒杀时会有多线程、多定位任务、多服务节点
对同一个redis中的key进行获取、更改和存储的操作。
如果每次进行操作时不进行加锁处理,就会导致数据不准确(多卖、少卖)的情况。
注:
博客:
霸道流氓气质的博客_CSDN博客-C#,架构之路,SpringBoot领域博主
关注公众号
霸道的程序猿
获取编程相关电子书、教程推送与免费下载。
1、Redisson
Redisson - Redis Java client
with features of an in-memory data grid。
Redisson - Redis Java 客户端
具有内存数据网格的特征。
官方文档地址:
目录 · redisson/redisson Wiki · GitHub
gitHub地址:
2、参考官方文档快速开始
引入maven依赖
- <dependency>
- <groupId>org.redissongroupId>
- <artifactId>redissonartifactId>
- <version>3.16.8version>
- 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
> futureArrayList = new ArrayList<>(); - //模拟50个任务发起购票/秒杀商品操作,每个任务买一个
- for (int i = 0; i < 50; i++) {
- Future
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
integerFuture = futureArrayList.get(i); - saleCount = saleCount + integerFuture.get();
- }
- System.out.println("累计卖出:"+saleCount);
关于多线程的使用方式可以参考下文
Java中ExecutorService线程池的使用(Runnable和Callable多线程实现):
Java中ExecutorService线程池的使用(Runnable和Callable多线程实现)_霸道流氓气质的博客-CSDN博客
这种不加任何锁的实现方式,导致的结果是
累计计数卖出的是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
> futureArrayList = new ArrayList<>(); - RLock numLock = redissonClient.getLock("numLock");
- for (int i = 0; i < 50; i++) {
- Future
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
integerFuture = futureArrayList.get(i); - saleCount = saleCount + integerFuture.get();
- }
- System.out.println("累计卖出:"+saleCount);
- }
- }
加锁效果