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)); }