这里是一个简单模拟秒杀的逻辑,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分布式锁的使用的更多相关文章

  1. 用压测模拟并发、并发处理(synchronized,redis分布式锁)

    使用工具:Apache an 测压命令: ab -n 100 -c 100 http://www.baidu.com -n代表模拟100个请求,-c代表模拟100个并发,相当于100个人同时访问 ab ...

  2. redis分布式锁实践

    分布式锁在多实例部署,分布式系统中经常会使用到,这是因为基于jvm的锁无法满足多实例中锁的需求,本篇将讲下redis如何通过Lua脚本实现分布式锁,不同于网上的redission,完全是手动实现的 我 ...

  3. Redis分布式锁的try-with-resources实现

    Redis分布式锁的try-with-resources实现 一.简介 在当今这个时代,单体应用(standalone)已经很少了,java提供的synchronized已经不能满足需求,大家自然 而 ...

  4. 关于分布式锁原理的一些学习与思考-redis分布式锁,zookeeper分布式锁

    首先分布式锁和我们平常讲到的锁原理基本一样,目的就是确保,在多个线程并发时,只有一个线程在同一刻操作这个业务或者说方法.变量. 在一个进程中,也就是一个jvm 或者说应用中,我们很容易去处理控制,在j ...

  5. Lua脚本在redis分布式锁场景的运用

    目录 锁和分布式锁 锁是什么? 为什么需要锁? Java中的锁 分布式锁 redis 如何实现加锁 锁超时 retry redis 如何释放锁 不该释放的锁 通过Lua脚本实现锁释放 用redis做分 ...

  6. Redis 分布式锁及缓存注释的使用方法

    使用工具:Apache an 测压命令: ab -n 100 -c 100 http://www.baidu.com -n代表模拟100个请求,-c代表模拟100个并发,相当于100个人同时访问 ab ...

  7. SpringBoot集成Redis分布式锁以及Redis缓存

    https://blog.csdn.net/qq_26525215/article/details/79182687 集成Redis 首先在pom.xml中加入需要的redis依赖和缓存依赖 < ...

  8. Redis分布式锁的实现

    前段时间,我在的项目组准备做一个类似美团外卖的拼手气红包[第X个领取的人红包最大],基本功能实现后,就要考虑这一操作在短时间内多个用户争抢同一资源的并发问题了,类似于很多应用如淘宝.京东的秒杀活动场景 ...

  9. springboot项目:Redis分布式锁的使用(模拟秒杀系统)

    模拟秒杀系统: 第一步:编写Service package com.payease.service; /** * liuxiaoming * 2017-12-14 */ public interfac ...

随机推荐

  1. encode(编码)和decode(解码)方法

    JS对文字进行编码涉及3个函数:escape,encodeURI,encodeURIComponent,相应3个解码函数:unescape,decodeURI,decodeURIComponent 1 ...

  2. supervisor 管理进程 基本用法

    1. 我们使用brew管理,先搜索一下确认是否有我们需要的软件包 # davis @ XiaoWeis-MacBook-Pro in ~ [16:48:42] $ brew search superv ...

  3. Windows上安装nodejs版本管理器nvm 安装成功之后重启终端失效

    nvm 安装成功之后重启终端失效(command not found) 安装nvm之后node不可用,“node”不是内部或外部命令,也不是可运行的程序或批处理文件(ng) 安装nvm: 下载nvm压 ...

  4. 用google translate大文件

    问题: google translate对于大文件不支持,咋办? 思路:自己写个函数把的文件拆成小文件,再用google translate! code: from googletrans impor ...

  5. Could not attach to pid : "xx"最近启动Xcode运行项目都会出现这个问题,再次启动或者多启动几次,就可以正常运行工程了。

    最近启动Xcode运行项目都会出现这个问题,再次启动或者多启动几次,就可以正常运行工程了. 普及一下:PID(进程控制符)英文全称为Process Identifier,它也属于电工电子类技术术语. ...

  6. C++中的break、continue、goto语句

    break.continue.goto break用于提前结束循环.只能打断一层循环.是把一层循环全部结束掉.continue则是提前结束循环内单次,继续循环下一步.

  7. Linux_CentOS 打包压缩和别名管理

    Linux 打包压缩命令 目前 linux 中打包和压缩的命令很多,最常用的方法有 zip.gzip.bzip2.xz.tar 1.zip 压缩包 1.制作 zip -r public.zip pub ...

  8. Dart接口

    /* 和Java一样,dart也有接口,但是和Java还是有区别的. 首先,dart的接口没有interface关键字定义接口,而是普通类或抽象类都可以作为接口被实现. 同样使用implements关 ...

  9. 008-MySQL报错-Access denied for user 'root'@'localhost' (using password: NO)

    1.新安装的mysql报错 MySQL报错-Access denied for user 'root'@'localhost' (using password: NO) 解决方案 1.先停掉原来的服务 ...

  10. 使用HSQLDB 客户端(jvm自带数据库使用技巧)

    数据库连接jar包 http://how2j.cn/frontdownload?bean.id=1169 hsqldb.jarservlet-2_3-fcs-classfiles.zipsqltool ...