1、单体应用中怎样处理并发问题:jdk1.6之后,Java原生锁性能得到了较大提升。因此使用Java原生锁就能解决并发问题。
@Transactional(rollbackFor = Exception.class)
public Result startSeckil(long seckillId,long userId) {
//锁住当前线程,缺点:用户体验差,响应时间太慢
Synchronized(this){
//校验库存
String nativeSql = "SELECT number FROM seckill WHERE seckill_id=?";
Object object = dynamicQuery.nativeQueryObject(nativeSql, new Object[]{seckillId});
Long number = ((Number) object).longValue();
if(number>0){
//扣库存
nativeSql = "UPDATE seckill SET number=number-1 WHERE seckill_id=?";
dynamicQuery.nativeExecuteUpdate(nativeSql, new Object[]{seckillId});
//创建订单
SuccessKilled killed = new SuccessKilled();
killed.setSeckillId(seckillId);
killed.setUserId(userId);
killed.setState((short)0);
killed.setCreateTime(new Timestamp(System.currentTimeMillis()));
dynamicQuery.save(killed);
String table = "success_killed_"+userId%8;
nativeSql = "INSERT INTO "+table+" (seckill_id, user_id,state,create_time)VALUES(?,?,?,?)";
Object[] params = new Object[]{seckillId,userId,(short)0,new Timestamp(System.currentTimeMillis())};
dynamicQuery.nativeExecuteUpdate(nativeSql,params);
//支付
return Result.ok(SeckillStatEnum.SUCCESS);
}else{
return Result.error(SeckillStatEnum.END);
}
}
}
2、Redis分布式锁(基于setNX)解决并发问题
@RequestMapping("/testlock")
public String deductStock() throws InterruptedException {
String lockKey = "product_001";
String clientId = UUID.randomUUID().toString(); //使用uuid当作value,为finally释放锁做准备
try {
//使用setnx命令(若key不存在,则新增;存在,不做操作),并设置失效时间(因为单机测试,所以设置短一点)
Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, clientId,10, TimeUnit.SECONDS);
if (!result) { //其他线程让其直接返回,保证只有一个线程 走下去
return "errorCode:当前商品正在抢购,请稍后再试";
}
int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock")); //获取库存
if (stock > 0) {
int realStock = stock - 1;
stringRedisTemplate.opsForValue().set("stock", realStock + ""); //设置最新库存
System.out.println("扣减成功,剩余库存:" + realStock + "");
} else {
System.out.println("扣减失败,库存不足");
}
} finally {
// 谁加的锁,谁释放(判断当前线程生成的锁是否是自己生成的)
if (clientId.equals(stringRedisTemplate.opsForValue().get(lockKey))) {
stringRedisTemplate.delete(lockKey);
}
}
return "抢购成功";
}
上述代码貌似看着没有什么问题,能够解决分布式并发问题,但是仔细分析代码就可以知道,如果A线程加锁成功并设置过期时间之后,在执行减库存之前由于网络延迟导致执行时间过长锁自动释放,这时B线程就能够获取锁成功继续执行代码导致读取到同一个库存值引发超卖问题。再有就是并发数太大有可能导致剩余大部分库存
Redission 很好的解决了这一问题,Redission获取锁不成功时,会循环等待获取锁,由于Redission源码操作redis数据库都是使用的Lua脚本,有不了解Lua的朋友可以先了解下Lua脚本
Redission分布式锁(基于Lua脚本以及“续命锁”)的实现
/**
* redisson方式 加锁
*/
@RequestMapping("/testlock")
public String deductStock() throws InterruptedException {
String lockKey = "product_001";
RLock redissonLock = redisson.getLock(lockKey); //获取锁
try {
// 加锁,实现锁续命功能
redissonLock.lock(); //加锁
int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock")); // jedis.get("stock")
if (stock > 0) {
int realStock = stock - 1;
stringRedisTemplate.opsForValue().set("stock", realStock + ""); // jedis.set(key,value)
System.out.println("扣减成功,剩余库存:" + realStock + "");
} else {
System.out.println("扣减失败,库存不足");
}
} finally {
redissonLock.unlock(); //释放锁
}
return "end";
}
redission加锁的方式不同点在于,在当前线程获取锁成功的同时,开启了一个子线程,定时判断当前主线程是否获得锁,如果获得,更新锁失效时间,默认10s判断一次,锁失效时间默认为30s,这就很好的解决了由于网络原因导致锁失效的问题。
思考:在redis集群中,假如master宕机,由于redis是弱一致性,那这时极有可能由于数据同步不一致,导致大量线程获得锁该怎么办?
若有侵权,请联系作者删除!