1、注意点:redis watch 命令用于监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。监控一直持续到EXEC命令(事务中的命令是在EXEC之后才执行的,所以在MULTI命令后可以修改WATCH监控的键值)

2:代码如下:


public class JedisRunnable implements Runnable {

    private Jedis jedis = JedisUtil.getJedis();
    private String userId;

    public JedisRunnable(String userId) {
        this.userId = userId;
    }

    public void run() {

        try {
            // 事务状态,如果监控的key没有发生改变,那么应该返回OK,事务也可以正常执行。
            jedis.watch(RedisSecKiller.WATCH_KEY);
            // 获取剩余商品数
            int leftGoodsNum = Integer.valueOf(jedis.get(RedisSecKiller.WATCH_KEY));
            // 当剩余商品数大于0时,才进行剩余商品数减1的事务操作。
            if (leftGoodsNum > 0) {
                // 开启jedis事务
                Transaction tx = jedis.multi();
                // 方法一:在事务中对键Goods对应的值做减1操作,此时tx.exec()的返回值的第一个元素是Goods对应的当前值。
                tx.decrBy(RedisSecKiller.WATCH_KEY, 1);
                // 方法二:在事务中设置Goods的值为原值减1,此时tx.exec()的返回值的第一个元素是"OK"。
//                tx.set(RedisSecKiller.WATCH_KEY, String.valueOf(leftGoodsNum - 1));
                // 执行事务,得到返回值。
                List<Object> results = tx.exec();
                // leftGoodsNum比键Goods对应的值大1,因为事务中刚执行了减1操作。
                // 由此可知,在当前事务中,leftGoodsNum与Goods对应的值(真实剩余商品数量)并不同步。
//                System.out.println("剩余商品数量:" + leftGoodsNum);
//                System.out.println("真实剩余商品数量:" + results);
                // results为null或空时,表示并发情况下用户没能抢购到商品,秒杀失败。
                if (results == null || results.isEmpty()) {
                    String failUserInfo = "fail---" + userId;
                    // 此时无法通过results.get(0)获取真实剩余商品数量。
                    String failMsg = "用户" + failUserInfo + ",抢购失败,剩余商品数量:" + leftGoodsNum +
                            ",但此时无法获取真实剩余商品数量。";
                    System.out.println(failMsg);
                    // 将秒杀失败的用户信息存入Redis。
                    jedis.setnx(failUserInfo, failMsg);
                } else { // 此时tx.exec()事务执行成功,会自动提交事务。
                    for (Object succ : results) {
                        String succUserInfo = "succ" + succ.toString() + "---" + userId;
                        String succMsg = "用户" + succUserInfo + ",抢购成功,当前抢购成功人数:" +
                                (10 - Integer.parseInt(results.get(0).toString())) +
                                ",真实剩余商品数量:" + Integer.parseInt(results.get(0).toString());
                        System.out.println(succMsg);
                        // 将秒杀成功的用户信息存入Redis。
                        jedis.setnx(succUserInfo, succMsg);
                    }
                }
            } else { // 此时库存为0,秒杀活动结束。
                String overUserInfo = "over---" + userId;
                String overMsg = "用户" + overUserInfo + ",商品被抢购完毕,剩余商品数量:" + leftGoodsNum;
                System.out.println(overMsg);
                // 将秒杀活动结束后还在访问秒杀系统的用户信息存入Redis。
                jedis.setnx(overUserInfo, overMsg);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JedisUtil.returnResource(jedis);
        }

    }
}

public class RedisSecKiller {

    // 模拟用户抢购最大并发数
    private static final int N_THREADS = 5;
    // jedis通过watch方法监控WATCH_KEY,一旦发生改变,事务将失败。
    public static final String WATCH_KEY = "Goods";
    // 商品总数
    private static final int GOODS_NUM = 1000;
    // 用户数量
    private static final int USER_NUM = 100;

    public static void main(String[] args) {
        // 创建线程池,模拟N_THREADS位用户同时抢购的过程。
        ExecutorService executorService = Executors.newFixedThreadPool(N_THREADS);
        Jedis jedis = JedisUtil.getJedis();
        // 设置商品总数为10
        jedis.set(WATCH_KEY, String.valueOf(GOODS_NUM));
        jedis.close();
        // 模拟USER_NUM位用户在抢购商品
        for (int i = 0; i < USER_NUM; i++) {
            executorService.execute(new JedisRunnable(UUID.randomUUID().toString()));
//            System.out.println("==============循环分割线===============");
        }
        executorService.shutdown();
    }
}


public class JedisUtil {

    private static final String ADDR = "192.168.222.130";
    private static final int PORT = 6379;
    private static final boolean TEST_ON_BORROW = true;
    private static final int MAX_IDLE = 200;
    private static JedisPool jedisPool = null;

    static {
        try {
            JedisPoolConfig config = new JedisPoolConfig();
            config.setMaxIdle(MAX_IDLE);
            config.setTestOnBorrow(TEST_ON_BORROW);
            jedisPool = new JedisPool(config, ADDR, PORT);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    public synchronized static Jedis getJedis() {
        try {
            if (jedisPool != null) {
                Jedis resource = jedisPool.getResource();
                return resource;
            }
            return null;
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return null;
    }

    public static void returnResource(final Jedis jedis) {
        if (jedis != null) {
            jedis.close();
        }
    }

}