场景
若依前后端分离版手把手教你本地搭建环境并运行项目:
若依前后端分离版手把手教你本地搭建环境并运行项目_霸道流氓气质的博客-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.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多线程实现):
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<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);
}
}
加锁效果
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/135916.html