synchronized的不足与redis分布式锁的使用
这里是一个简单模拟秒杀的逻辑,stock和orders为两个Map,分别模拟库存表和订单表
public void orderProductMockDiffUser(String productId)
{
//1.查询该商品库存,为0则秒杀活动结束。
int stockNum = stock.get(productId);
if(stockNum == 0) {
throw new SellException(100,"活动结束");
}else {
//2.下单(模拟不同用户id不同)
orders.put(KeyUtil.genUniqueKey(),productId);
//3.减库存(模拟在内存(或redis)中减库存)
stockNum =stockNum-1;
try {
//4.模拟一些IO或其他业务操作
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
stock.put(productId,stockNum);
}
}
这段逻辑存在的问题是当并发量大的时候,会造成卖出的商品数与库存减去的数目不一致

我们可以使用synchronized关键字来解决这个问题,在方法名上加上synchronized
public synchronized void orderProductMockDiffUser(String productId)
虽然synchronized可以解决数目不一致的问题,但是缺点也很明显,那就是慢,因为synchronized修饰的方法是同步的,也就是说每次只有一个线程访问这个方法,而且synchronized只适用于单点的情况。
更好的方法是使用redis分布式锁
@Component
@Slf4j
public class RedisLock { @Autowired
private StringRedisTemplate redisTemplate; /**
* 加锁
* @param key 商品id
* @param value 当前时间+超时时间
* @return
*/
public boolean lock(String key, String value) {
//setIfAbsent()也就是redis的setnx,当key不存在时设置value
if(redisTemplate.opsForValue().setIfAbsent(key, value)) {
//加锁成功
return true;
}
//当锁已存在,可以获取该锁的value,来判断是否过期
String currentValue = redisTemplate.opsForValue().get(key);
//如果锁过期
if (!StringUtils.isEmpty(currentValue)
&& Long.parseLong(currentValue) < System.currentTimeMillis()) {
//获取上一个锁的时间
String oldValue = redisTemplate.opsForValue().getAndSet(key, value);
//如果多个线程同时进入这里,则可以通过判断oldValue与currentValue是否相等来限制多个线程加锁
if (!StringUtils.isEmpty(oldValue) && oldValue.equals(currentValue)) {
return true;
}
} return false;
} /**
* 解锁
* @param key
* @param value
*/
public void unlock(String key, String value) {
try {
String currentValue = redisTemplate.opsForValue().get(key);
if (!StringUtils.isEmpty(currentValue) && currentValue.equals(value)) {
redisTemplate.opsForValue().getOperations().delete(key);
}
}catch (Exception e) {
log.error("【redis分布式锁】解锁异常, {}", e);
}
} }
这样我们只要在秒杀逻辑开始时加上锁,逻辑结束后解锁就可以了。redis分布式锁不仅比synchronized更快,而且也适用于分布式。
public void orderProductMockDiffUser(String productId)
{
//加锁
long time=System.currentTimeMillis()+TIMEOUT;
if(!redisLock.lock(productId,String.valueOf(time))){
throw new SellException(ResultEnum.REDIS_LOCK_FAIL);
}
//1.查询该商品库存,为0则活动结束。
int stockNum = stock.get(productId);
if(stockNum == 0) {
throw new SellException(100,"活动结束");
}else {
//2.下单(模拟不同用户openid不同)
orders.put(KeyUtil.genUniqueKey(),productId);
//3.减库存(模拟在内存(或redis)中减库存)
stockNum =stockNum-1;
try {
//4.模拟一些IO或其他业务操作
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
stock.put(productId,stockNum);
} //解锁
redisLock.unlock(productId,String.valueOf(time));
}
synchronized的不足与redis分布式锁的使用的更多相关文章
- 用压测模拟并发、并发处理(synchronized,redis分布式锁)
使用工具:Apache an 测压命令: ab -n 100 -c 100 http://www.baidu.com -n代表模拟100个请求,-c代表模拟100个并发,相当于100个人同时访问 ab ...
- redis分布式锁实践
分布式锁在多实例部署,分布式系统中经常会使用到,这是因为基于jvm的锁无法满足多实例中锁的需求,本篇将讲下redis如何通过Lua脚本实现分布式锁,不同于网上的redission,完全是手动实现的 我 ...
- Redis分布式锁的try-with-resources实现
Redis分布式锁的try-with-resources实现 一.简介 在当今这个时代,单体应用(standalone)已经很少了,java提供的synchronized已经不能满足需求,大家自然 而 ...
- 关于分布式锁原理的一些学习与思考-redis分布式锁,zookeeper分布式锁
首先分布式锁和我们平常讲到的锁原理基本一样,目的就是确保,在多个线程并发时,只有一个线程在同一刻操作这个业务或者说方法.变量. 在一个进程中,也就是一个jvm 或者说应用中,我们很容易去处理控制,在j ...
- Lua脚本在redis分布式锁场景的运用
目录 锁和分布式锁 锁是什么? 为什么需要锁? Java中的锁 分布式锁 redis 如何实现加锁 锁超时 retry redis 如何释放锁 不该释放的锁 通过Lua脚本实现锁释放 用redis做分 ...
- Redis 分布式锁及缓存注释的使用方法
使用工具:Apache an 测压命令: ab -n 100 -c 100 http://www.baidu.com -n代表模拟100个请求,-c代表模拟100个并发,相当于100个人同时访问 ab ...
- SpringBoot集成Redis分布式锁以及Redis缓存
https://blog.csdn.net/qq_26525215/article/details/79182687 集成Redis 首先在pom.xml中加入需要的redis依赖和缓存依赖 < ...
- Redis分布式锁的实现
前段时间,我在的项目组准备做一个类似美团外卖的拼手气红包[第X个领取的人红包最大],基本功能实现后,就要考虑这一操作在短时间内多个用户争抢同一资源的并发问题了,类似于很多应用如淘宝.京东的秒杀活动场景 ...
- springboot项目:Redis分布式锁的使用(模拟秒杀系统)
模拟秒杀系统: 第一步:编写Service package com.payease.service; /** * liuxiaoming * 2017-12-14 */ public interfac ...
随机推荐
- Nginx模块说明
一.Nginx内置模块 -–prefix= #指向安装目录 -–sbin-path #指向(执行)程序文件(nginx) -–conf-path= #指向配置文件(nginx.conf) -–erro ...
- rsync 使用ssh协议免密
rsync远程传输避免密码输入 每次rsync远程传输时都需要输入用户在远程机器上的密码,这样导致无法在后台自动运行rsync,可采用秘钥文件来替代人工输入密码的方式来解决. 第一步 在本地机器上使用 ...
- arcpy 重分类
arcpy.gp.Reclassify_sa("dem.tif","Value","0 2000 1;2000 2100 2;2100 2500 3; ...
- 文献阅读 | The single-cell transcriptional landscape of mammalian organogenesis | 器官形成 | 单细胞转录组
The single-cell transcriptional landscape of mammalian organogenesis 老板已经提了无数遍的文章,确实很nb,这个工作是之前我们无法想 ...
- python统计apache、nginx访问日志IP访问次数并且排序(显示前20条)【转】
前言:python统计apache.nginx访问日志IP访问次数并且排序(显示前20条).其实用awk+sort等命令可以实现,用awk数组也可以实现,这里只是用python尝试下. apach ...
- Javascript之hoisting变量提升
javascript不仅仅是一门弱类型语言,还是一门解释型语言.一门编程语言的本质就是这样,优点即是缺点,缺点也往往是优点.JS因为有了变量提升,能够使我们在编程时可以忽略“先声明,再使用”的规则,但 ...
- flutter Form表单
import 'package:flutter/material.dart'; class FormDemo extends StatelessWidget { @override Widget bu ...
- shell编程系列7--shell中常用的工具find、locate、which、whereis
shell编程系列7--shell中常用的工具find.locate.which.whereis .文件查找之find命令 语法格式:find [路径] [选项] [操作] 选项 -name 根据文件 ...
- pytorch 计算图像数据集的均值和标准差
在使用 torchvision.transforms进行数据处理时我们经常进行的操作是: transforms.Normalize((0.485,0.456,0.406), (0.229,0.224, ...
- xshell连接中标麒麟
中标麒麟: 1.首先肯定需要给虚拟机配置静态ip,让虚拟机作为独立的ip存在 https://www.cnblogs.com/judes/p/11776872.html 2.输入rpm -qa | g ...