redis事务模拟商品抢购,记Runnable::new 和 new Runnable使用注意事项

在使用redis事务模拟商品抢购时,模拟高并发抢商品中发现runnable::new会导致run不执行

 Jedis jedis1 = getJedis();
        jedis1.set("shop","10");
        jedis1.close();

        List list = new ArrayList<>(100);
        List count = new ArrayList<>();
        for (int i = 0; i < 100; i++) {
            //使用::
            list.add(JedisRunnable::new);
            //使用new 实例
//            list.add(new JedisRunnable());
            //使用() -> 
//            list.add(() -> {
//                System.out.println("==============循环分割线===============");
//                try (Jedis jedis = JedisDemo.getJedis()){
//                    // 事务状态,如果监控的key没有发生改变,那么应该返回OK,事务也可以正常执行。
//                    jedis.watch("shop");
//
//                    // 获取剩余商品数
//                    int leftGoodsNum = Integer.valueOf(jedis.get("shop"));
//
//                    // 当剩余商品数大于0时,才进行剩余商品数减1的事务操作。
//                    if (leftGoodsNum > 0) {
//                        // 开启jedis事务
//                        Transaction tx = jedis.multi();
//                        // 方法一:在事务中对键Goods对应的值做减1操作,此时tx.exec()的返回值的第一个元素是Goods对应的当前值。
//                        tx.decrBy("shop", 1);
//                        // 执行事务,得到返回值。
//                        List results = tx.exec();
//                        // results为null或空时,表示并发情况下用户没能抢购到商品,秒杀失败。
//                        if (results == null || results.isEmpty()) {
//                            // 此时无法通过results.get(0)获取真实剩余商品数量。
//                            String failMsg ="抢购失败,剩余商品数量:"+ leftGoodsNum +
//                                    ",但此时无法获取真实剩余商品数量。";
//                            System.out.println(failMsg);
//                            // 将秒杀失败的用户信息存入Redis。
//                        } else { // 此时tx.exec()事务执行成功,会自动提交事务。
//                            for(Object succ : results) {
//                                String succUserInfo ="succ" + succ.toString() + "---";
//                                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);
//                                count.add(Integer.parseInt(succ.toString()));
//                            }
//                        }
//                    } else { // 此时库存为0,秒杀活动结束。
//                        String overUserInfo ="over---";
//                        String overMsg = "用户" + overUserInfo + ",商品被抢购完毕,剩余商品数量:" + leftGoodsNum;
//                        System.out.println(overMsg);
//                        // 将秒杀活动结束后还在访问秒杀系统的用户信息存入Redis。
//                        jedis.setnx(overUserInfo, overMsg);
//                    }
//                } catch (Exception e) {
//                    e.printStackTrace();
//                }
//            });
        }

        CompletableFuture[] completableFutures = list.stream().map(CompletableFuture::runAsync)
                .toArray(CompletableFuture[]::new);
        CompletableFuture.allOf(completableFutures).get(); 
  

实测过程中发现 使用 ::方式 抢购的时候无任何执行,另两种方式正常

断点发现

::方式,会发现completableFutures中的Future元素都为Completed normally状态

redis事务模拟商品抢购,记Runnable::new 和 new Runnable使用注意事项_第1张图片

另两种方式为,Not completed,此状态只需要继续调用get方法就可以了

redis事务模拟商品抢购,记Runnable::new 和 new Runnable使用注意事项_第2张图片

 区别: 第一种方式调用的时候在编译进completableFutures中的时候就被自动执行过了。

             其它方式说明runnable待执行状态

什以原因导致编译时VM觉得的可以直接帮你执行了run呢,所以怀疑这两种方式最终在add的时候就不是同一个Runnable

实验:在JedisRunnable中加入一个变量

断点测试:

redis事务模拟商品抢购,记Runnable::new 和 new Runnable使用注意事项_第3张图片

redis事务模拟商品抢购,记Runnable::new 和 new Runnable使用注意事项_第4张图片

 以上测试结果充分说明JedisRunnable::new的值并未被add进list,而是被一个runnable替代了

通过lambda表达式替换可以发现

 其中JedisRunnable::new  与 () -> new JedisRunnable() 一样,而 () -> 本身就是返回一个 runnable,这个没有返回值的runnable只是创建了一个无用JedisRunnable

可见确实被替换了。

结果记录, 

redis事务模拟商品抢购,记Runnable::new 和 new Runnable使用注意事项_第5张图片

使用runnable::new需要变形成

Supplier r = () -> {return JedisRunnable::new;};

  r.get();

总结: 在将runnable为参数时不要用runnable::new,虽然new runnable返回的是runnable,但是::外层包裹了一个 () -> 函数。可以不用新建实现runnable的类,而直接在类中写方法,然后使用 类::方法

JedisRunMethod::runMethod作为参数

参考:

java - Runnable::new vs new Runnable() - Stack Overflow

java - Runnable :: new与new Runnable() - Thinbug     (答案1)

你可能感兴趣的:(error,study,java,多线程,redis)