redis的队列来实现。将要促销的商品数量以队列的方式存入redis中,每当用户抢到一件促销商品则从队列中删除一个数据,确保商品不会超卖。这个操作起来很方便,而且效率极高



Redis是一个分布式key-value缓存系统,value支持多种数据结构,这里value可以选择两种类型,String(或者hash):主要用于记录商品的库存,对商品减库存。Set集合(这里不要用list集合,list集合是可重复的,set是不可重复的,可以保证一个用户只卖一次,如果一个用户可以买多次那么可以使用list集合):用于存储用户的id获取其他唯一确定一个用户的值。

在秒杀开始前:可以使用批处理,将参加秒杀的产品信息缓存到redis中。这里将产品的业务唯一字段作为key,库存作为value。这里的key要和前端缓存的key一致。
在秒杀开始时:用户大量提交。根据用户提交的产品信息,获取到redis中需要的key值,查询缓存 ,得到库存量,判断当前库存是否大于零,如果大于零,判断当前的set集合中是否用该用户ID,如果没有,减库存并且将用户的ID放入集合中,并对库存减一,如果库存为0,提示用户,商品已售完等文案信息,如果集合中已经存在该用户id,则不做任何处理,直接处理下一个请求。直到最后库存售完,上面的过程可以利用redis事务和watch功能完成对数据一致性的控制即超卖问题
库存售完后:程序开始启动一个有个后台线程,可以阻塞等待商品库存售完的通知,在上面一步,库存一旦售完,后台进程获取set集合中的用户信息,异步处理需要操作的购买等后续操作。


package com.github.distribute.lock.redis;                                                                                                          
                                                                                                                                                   
import java.util.List;                                                                                                                             
import java.util.Set;                                                                                                                              
import java.util.concurrent.ExecutorService;                                                                                                       
import java.util.concurrent.Executors;                                                                                                             
                                                                                                                                                   
import redis.clients.jedis.Jedis;                                                                                                                  
import redis.clients.jedis.Transaction;                                                                                                            
                                                                                                                                                   
/**                                                                                                                                                
 * redis乐观锁实例                                                                                                                                      
 * @author linbingwen                                                                                                                              
 *                                                                                                                                                 
 */                                                                                                                                                
public class OptimisticLockTest {                                                                                                                  
                                                                                                                                                   
    public static void main(String[] args) throws InterruptedException {                                                                           
        long starTime=System.currentTimeMillis();                                                                                                  
                                                                                                                                                   
        initPrduct();                                                                                                                              
        initClient();                                                                                                                              
        printResult();                                                                                                                             
                                                                                                                                                   
        long endTime=System.currentTimeMillis();                                                                                                   
        long Time=endTime-starTime;                                                                                                                
        System.out.println("程序运行时间: "+Time+"ms");                                                                                                  
                                                                                                                                                   
    }                                                                                                                                              
                                                                                                                                                   
    /**                                                                                                                                            
     * 输出结果                                                                                                                                        
     */                                                                                                                                            
    public static void printResult() {                                                                                                             
        Jedis jedis = RedisUtil.getInstance().getJedis();                                                                                          
        Set<String> set = jedis.smembers("clientList");                                                                                            
                                                                                                                                                   
        int i = 1;                                                                                                                                 
        for (String value : set) {                                                                                                                 
            System.out.println("第" + i++ + "个抢到商品,"+value + " ");                                                                                  
        }                                                                                                                                          
                                                                                                                                                   
        RedisUtil.returnResource(jedis);                                                                                                           
    }                                                                                                                                              
                                                                                                                                                   
    /*                                                                                                                                             
     * 初始化顾客开始抢商品                                                                                                                                  
     */                                                                                                                                            
    public static void initClient() {                                                                                                              
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();                                                                        
        int clientNum = 10000;// 模拟客户数目                                                                                                            
        for (int i = 0; i < clientNum; i++) {                                                                                                      
            cachedThreadPool.execute(new ClientThread(i));                                                                                         
        }                                                                                                                                          
        cachedThreadPool.shutdown();                                                                                                               
                                                                                                                                                   
        while(true){                                                                                                                               
            if(cachedThreadPool.isTerminated()){                                                                                                   
                System.out.println("所有的线程都结束了!");                                                                                                  
                break;                                                                                                                             
            }                                                                                                                                      
            try {                                                                                                                                  
                Thread.sleep(1000);                                                                                                                
            } catch (InterruptedException e) {                                                                                                     
                e.printStackTrace();                                                                                                               
            }                                                                                                                                      
        }                                                                                                                                          
    }                                                                                                                                              
                                                                                                                                                   
    /**                                                                                                                                            
     * 初始化商品个数                                                                                                                                     
     */                                                                                                                                            
    public static void initPrduct() {                                                                                                              
        int prdNum = 100;// 商品个数                                                                                                                   
        String key = "prdNum_100001";                                                                                                              
        String clientList = "clientList";// 抢购到商品的顾客列表                                                                                             
        Jedis jedis = RedisUtil.getInstance().getJedis();                                                                                          
                                                                                                                                                   
        if (jedis.exists(key)) {                                                                                                                   
            jedis.del(key);                                                                                                                        
        }                                                                                                                                          
                                                                                                                                                   
        if (jedis.exists(clientList)) {                                                                                                            
            jedis.del(clientList);                                                                                                                 
        }                                                                                                                                          
                                                                                                                                                   
        jedis.set(key, String.valueOf(prdNum));// 初始化                                                                                              
        RedisUtil.returnResource(jedis);                                                                                                           
    }                                                                                                                                              
                                                                                                                                                   
}                                                                                                                                                  
                                                                                                                                                   
/**                                                                                                                                                
 * 顾客线程                                                                                                                                            
 *                                                                                                                                                 
 * @author linbingwen                                                                                                                              
 *                                                                                                                                                 
 */                                                                                                                                                
class ClientThread implements Runnable {                                                                                                           
    Jedis jedis = null;                                                                                                                            
    String key = "prdNum_10001";// 商品主键                                                                                                            
    String clientList = "clientList"; 抢购到商品的顾客列表主键                                                                                             
    String clientName;                                                                                                                             
                                                                                                                                                   
    public ClientThread(int num) {                                                                                                                 
        clientName = "编号=" + num;                                                                                                                  
    }                                                                                                                                              
                                                                                                                                                   
    public void run() {                                                                                                                            
        try {                                                                                                                                      
            Thread.sleep((int)(Math.random()*5000));// 随机睡眠一下                                                                                      
        } catch (InterruptedException e1) {                                                                                                        
        }                                                                                                                                          
        while (true) {                                                                                                                             
            System.out.println("顾客:" + clientName + "开始抢商品");                                                                                      
            jedis = RedisUtil.getInstance().getJedis();                                                                                            
            try {                                                                                                                                  
                jedis.watch(key);                                                                                                                  
                int prdNum = Integer.parseInt(jedis.get(key));// 当前商品个数                                                                            
                if (prdNum > 0) {                                                                                                                  
                    Transaction transaction = jedis.multi();                                                                                       
                    transaction.set(key, String.valueOf(prdNum - 1));                                                                              
                    List<Object> result = transaction.exec();                                                                                      
                    if (result == null || result.isEmpty()) {                                                                                      
                        System.out.println("悲剧了,顾客:" + clientName + "没有抢到商品");// 可能是watch-key被外部修改,或者是数据操作被驳回                                      
                    } else {                                                                                                                       
                        jedis.sadd(clientList, clientName);// 抢到商品记录一下                                                                             
                        System.out.println("好高兴,顾客:" + clientName + "抢到商品");                                                                       
                        break;                                                                                                                     
                    }                                                                                                                              
                } else {                                                                                                                           
                    System.out.println("悲剧了,库存为0,顾客:" + clientName + "没有抢到商品");                                                                    
                    break;                                                                                                                         
                }                                                                                                                                  
            } catch (Exception e) {                                                                                                                
                e.printStackTrace();                                                                                                               
            } finally {                                                                                                                            
                jedis.unwatch();                                                                                                                   
                RedisUtil.returnResource(jedis);                                                                                                   
            }                                                                                                                                      
                                                                                                                                                   
        }                                                                                                                                          
    }                                                                                                                                              
                                                                                                                                                   
}