线程池使用案例一

前题

刚入职的公司,要求接口压测要通过,发现在业务中需要接口需要使用多线程的使用,由于入参动态输入会员id数组,我们需要创建数组size的线程数,来请求db(其实应该用一个sql来写的,但是公司分片数据库不支持)。
需求:多线程中,每个线程执行的时间都不能超过一个固定值,如果返回不了,可丢弃

方案一

使用CompletableFutureallOf join 来实现,但又缺点,我们需要在规定时间200ms内获取所有Future的结果,但是好像这样的代码并不适合

// multiple task
        List<Integer> taskList = new ArrayList<Integer>() {{
            add(1);
            add(2);
            add(3);
            add(4);
        }};


        // collect completeFutureList
        List<CompletableFuture<String>> completableFutureList = new ArrayList<>();
        for (Integer task : taskList) {
            CompletableFuture completableFuture = CompletableFuture.supplyAsync(() -> {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                return task.toString() + "-----";
            });
            completableFutureList.add(completableFuture);
        }
        // all task complete
        CompletableFuture.allOf(completableFutureList.toArray(new CompletableFuture[completableFutureList.size()])).join();

        // get result
        for (CompletableFuture completableFuture : completableFutureList) {
            try {
                System.out.println(completableFuture.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }

方案二

使用线程池+callable+CountDownLatch来实现 (当前方法有并发问题)

final static ExecutorService executor = Executors.newCachedThreadPool();

    public static void main(String[] args) {


        // multiple task
        List<Integer> taskList = new ArrayList<Integer>() {{
            add(1);
            add(2);
            add(3);
            add(4);
        }};

        // create countDownLatch
        CountDownLatch countDownLatch = new CountDownLatch(taskList.size());

        // future list
        List<Future<String>> futureList = new ArrayList<>();
        for (Integer taskId:taskList) {
            Future<String> future = executor.submit(new Callable<String>() {
                @Override
                public String call() throws Exception {
                    TimeUnit.SECONDS.sleep(taskId);
                    countDownLatch.countDown();
                    return String.valueOf(taskId);
                }
            });
            futureList.add(future);
        }

        // wait 2 seconds
        try {
            countDownLatch.await(2,TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // get future result right now
        List<String> result = new ArrayList<>();
        for (Future<String> future : futureList){
            String s = null;
            try {
                s = future.get(0, TimeUnit.SECONDS);
                result.add(s);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            } catch (TimeoutException e) {
                e.printStackTrace();
            }
        }
        System.out.println(result);


    }

下面贴 测试出来的bug

 // 模拟1000并发请求,会发现下面的get时 会有几率报错。在压测的时候,10w请求,大概1000+个错误
        for (int i = 0; i < 1000; i++) {
            executor.submit(() -> {


                CountDownLatch latch = new CountDownLatch(1);
                Future<String> submit = executor.submit(() -> {

                    latch.countDown();
                    /**
                     * 猜测,这里出现了cpu切换
                     * */
                    return "abc";


                });

                try {
                    latch.await(10, TimeUnit.MILLISECONDS);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                try {

                    System.out.println( submit.get(0, TimeUnit.MILLISECONDS));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (ExecutionException e) {
                    e.printStackTrace();
                } catch (TimeoutException e) {
                    e.printStackTrace();
                }


            });

        }

方案三

使用 invokeAll 加时间的形式

for (int i = 0; i < 100000; i++) {
    // 模拟请求
    executor.execute(() -> {
        //
        List<Callable<Integer>> taskList = new ArrayList<>();
        taskList.add(new Callable() {
            @Override
            public Object call() throws Exception {
                try {
                    // 模拟sql
                    TimeUnit.MILLISECONDS.sleep(20);
                    return 1;
                    //
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                return -1025;
            }
        });
        taskList.add(new Callable() {
            @Override
            public Object call() throws Exception {
                try {
                    // 模拟sql
                    TimeUnit.MILLISECONDS.sleep(10);
                    return 1;
                } catch (InterruptedException e) {
                    System.out.println("error");
                }
                return -1024;
            }
        });

        //
        List<Future<Integer>> futures = null;
        try {
            // 问题所在 1 ?
            futures = executor.invokeAll(taskList,200,TimeUnit.MILLISECONDS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        List<Integer> result = new ArrayList<>();
        for (Future<Integer> future : futures) {

            Integer integer = 0;
            try {
                // 问题所在 2 ?
                future.cancel(true);
                if (!future.isCancelled()) {
                    integer = future.get();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            result.add(integer);
        }
    });

}

这个使用触发一个druid的异常

参考

private DruidPooledConnection getConnectionInternal(long maxWait) throws SQLException {
   // 省略
    try {
        lock.lockInterruptibly();
    } catch (InterruptedException e) {
        connectErrorCountUpdater.incrementAndGet(this);
        throw new SQLException("interrupt", e);
    }
    // 省略,数据库连接池配置的大小参数等检查  
  
}

1.在于future.cancel(true)会触发interrupt
2.invokeAll(taskList,200,TimeUnit.MILLISECONDS)里面的finally也会调用future.cancel(true);会触发interrupt

第四种方案 (最终版本)

就使用invokeAll,不带时间的形式。
主要是我们看,线上的接口响应时间大多符合要求,于是去掉

for (int i = 0; i < 100000; i++) {
            // 模拟请求
            executor.execute(() -> {
                //
                List<Callable<Integer>> taskList = new ArrayList<>();
                taskList.add(new Callable() {
                    @Override
                    public Object call() throws Exception {
                        try {
                            // 模拟sql
                            TimeUnit.MILLISECONDS.sleep(20);
                            return 1;
                            //
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        return -1025;
                    }
                });
                taskList.add(new Callable() {
                    @Override
                    public Object call() throws Exception {
                        try {
                            // 模拟sql
                            TimeUnit.MILLISECONDS.sleep(10);
                            return 1;
                        } catch (InterruptedException e) {
                            System.out.println("error");
                        }
                        return -1024;
                    }
                });

                //
                List<Future<Integer>> futures = null;
                try {
                    futures = executor.invokeAll(taskList);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                List<Integer> result = new ArrayList<>();
                for (Future<Integer> future : futures) {

                    Integer integer = 0;
                    try {
                        if (!future.isCancelled()) {
                            integer = future.get();
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    result.add(integer);
                }
            });

        }
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值