1. 程式人生 > >Java 悲觀鎖Pessimistic-Lock常用實現場景

Java 悲觀鎖Pessimistic-Lock常用實現場景

1:目前主流庫存秒殺或者限流都是採用悲觀鎖Pessimistic-Lock機制,主要是安全,在秒殺量不大情況下速度還是比較快的。

2:比如我遇到的場景,10萬人來搶購商品,(1)限流進來,比如實際只放2000人進來搶購。(2)機器壓力夠那直接10萬用戶直接來搶商品,在庫存搶完後的一定時間內把搶購置灰即可。

 

 

(1)環境:mysql + jdbctemplate

 

(2)商品表goods:

DROP TABLE IF EXISTS `goods`;
CREATE TABLE `goods` (
  `id` int(11) unsigned NOT
NULL AUTO_INCREMENT, `name` varchar(100) DEFAULT NULL COMMENT '商品名稱', `stock` int(11) unsigned NOT NULL COMMENT '商品庫存', `version` int(2) DEFAULT NULL COMMENT '版本號', `token_time` datetime NOT NULL COMMENT '樂觀鎖時間戳', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8;
-- ---------------------------- -- Records of goods -- ---------------------------- BEGIN; INSERT INTO `goods` VALUES (1, 'product', 9999, 1, '2018-11-30 22:06:20'); COMMIT; SET FOREIGN_KEY_CHECKS = 1;

 

(3)DAO層程式碼:

package org.yugh.goodsstock.pessimistic_lock.repository;

import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository; import javax.annotation.Resource; import java.util.List; import java.util.Map; /** * @author: YuGenHai * @name: SeckRepository * @creation: 2018/11/28 00:30 * @notes: 悲觀鎖DAO層 * @notes: 悲觀鎖需要注意mysql自帶自動commit,用行鎖需要開啟事務 set transation 或者set autocommit =0 * 防止自動提交,set autocommit =1 自動提交 */ @Repository public class PessimisticLockRepository { /** * 測試使用 {@link JdbcTemplate} */ @Resource private JdbcTemplate jdbcTemplate; /** * 獲取現有庫存量 * @param id * @return * @author yugenhai */ public int queryStock(long id) { //開啟事務 String lock = "set autocommit=0"; jdbcTemplate.update(lock); //獲得當前庫存 並上鎖 String sql = "select * from goods where id=1 for update"; List<Map<String,Object>> list = jdbcTemplate.queryForList(sql); if(null != list && list.size() > 0){ Map<String,Object> map = list.get(0); System.out.println("當前庫存值: "+ map.get("stock")); return Integer.valueOf(String.valueOf(map.get("stock"))); } return 0; } /** * 還有庫存量,並且要釋放當前鎖 * @author yugenhai * @return */ public int updateStock() { String update = "update goods set stock=stock-1 where id=1"; jdbcTemplate.update(update); String unlock = "commit"; jdbcTemplate.update(unlock); return 1; } /** * 商品被搶光後需要釋放 * @author yugenhai * @return */ public int unlock(){ String unlock = "commit"; jdbcTemplate.update(unlock); return 1; } }

 

(4)測試悲觀鎖:

package org.yugh.goodsstock.pessimistic_lock;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.yugh.goodsstock.pessimistic_lock.repository.PessimisticLockRepository;

import javax.annotation.Resource;

/**
 * @author: YuGenHai
 * @name: PessimisticLockTest
 * @creation: 2018/11/28 00:32
 * @notes: 悲觀鎖測試秒殺商品
 */
@RunWith(SpringRunner.class)
@SpringBootTest
public class PessimisticLockTest {

    @Resource
    PessimisticLockRepository pessimisticLockRepository;

    /**
     * STOCK庫存總數,測試可以理解為購買者
     * 表裡的stock對應庫存
     */
    private static final int STOCK = 10000;

    /**
     * 悲觀鎖秒殺商品
     * @author yugenhai
     */
    @Test
    public void pessimisticLockTest() {
        long beTime = System.currentTimeMillis();
        for (int i = 0; i < STOCK; i++) {
            //獲得當前庫存
            //順帶上鎖,開啟事務
            int stock = pessimisticLockRepository.queryStock(1);
            if (stock > 0) {
                //庫存還有
                //當前使用者繼續秒殺一個商品 並提交事務 釋放鎖
                pessimisticLockRepository.updateStock();
                System.out.println(new Thread().getName() + " 搶到了第 " + (i + 1) + " 商品");
            } else {
                //沒有庫存後釋放鎖
                System.err.println(new Thread().getName() + " 抱歉,商品沒有庫存了!");
                pessimisticLockRepository.unlock();
                //break;
            }
        }
        System.out.println("秒殺 "+ STOCK + " 件商品使用悲觀鎖需要花費時間:" + (System.currentTimeMillis() - beTime));

    }


}

 

 

(5)模擬10000個使用者搶購9999個商品,最後一位使用者沒有搶到:

當前庫存值: 8
Thread-9994 搶到了第 9992 商品
當前庫存值: 7
Thread-9995 搶到了第 9993 商品
當前庫存值: 6
Thread-9996 搶到了第 9994 商品
當前庫存值: 5
Thread-9997 搶到了第 9995 商品
當前庫存值: 4
Thread-9998 搶到了第 9996 商品
當前庫存值: 3
Thread-9999 搶到了第 9997 商品
當前庫存值: 2
Thread-10000 搶到了第 9998 商品
當前庫存值: 1
Thread-10001 搶到了第 9999 商品
當前庫存值: 0
秒殺 10000 件商品使用悲觀鎖需要花費時間:9922
Thread-10002 抱歉,商品沒有庫存了!
2018-12-01 00:51:06.914  INFO 9125 --- [       Thread-2] s.c.a.AnnotationConfigApplicationContext : Closing org.spring[email protected]f0da945: startup date [Sat Dec 01 00:50:56 CST 2018]; root of context hierarchy
2018-12-01 00:51:06.915  INFO 9125 --- [       Thread-2] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown initiated...
2018-12-01 00:51:06.920  INFO 9125 --- [       Thread-2] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown completed.

 

3:悲觀鎖Pessimistic-Lock 是非常安全的,有序的購買,必須等待上一個事務提交釋放後才能繼續操作。但也是悲觀看待事情的,比如速度、效能問題。

專案程式碼下載 https://github.com/yugenhai108/goods-stock