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

 

posted @ 2019-09-10 17:42  skychmz  阅读(3452)  评论(0编辑  收藏  举报