這裏是一個簡單模擬秒殺的邏輯,stock和orders爲兩個Map,分別模擬庫存表和訂單表redis
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只適用於單點的狀況。spa
更好的方法是使用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更快,並且也適用於分佈式。code
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)); }