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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM