tips:如果想要了解线程池和程序计数器可以参考上一篇文章。
synListItem = Collections.synchronizedList(new ArrayList<>());
//创建一个CountDownLatch类,构造入参线程数
CountDownLatch countDownLatch = new CountDownLatch(listItem.size());
Iterator<Map<String, Object>> iterator = listItem.iterator();
final ExecutorService es = ExecutorFactory.getEs();
while (iterator.hasNext()) {
// 线程满导致异常,程序计数器未执行
try {
es.submit(() -> {
Map<String, Object> map = iterator.next();
// linkIds 为空表示开关未配置
try {
} catch (Exception e) {
e.printStackTrace();
} finally {
//该线程执行完毕-1
countDownLatch.countDown();
}
});
} catch (Exception e) {
e.printStackTrace();
} finally {
//该线程执行完毕-1
countDownLatch.countDown();
}
}
try {
countDownLatch.await(200, TimeUnit.MILLISECONDS);
log.info("after menu onsale filter listItem size:{}", synListItem.size());
log.debug("after menu onsale filter listItem:{}", synListItem);
} catch (InterruptedException e) {
e.printStackTrace();
}
需求:异步等待访问一个性能不那么好的网络I/O,服务的qps 达到400
这次主要着重于线程池 和 try catch finally 和 countDownLatch.countDown(); 讨论
1.遇到的第一个问题:
ExecutorService 创建线程池,第一次使用的时候使用Executors.newCachedThreadPool(); 缓存线程池去使用,上线后没有明显的报错,但是!我发现线程池的数据和线程的数量在急速增加!!!我明显慌了!这样下去可不行,然后就换成了newFixedThreadPool(n);但是发现一直显示线程满了。相似的例图如下:
思考:1.为什么线程池变成固定的会线程不够
2.每个请求new 一个线程池,是不是不够优雅,违背线程池全局管理的定义。
行动:1.变成全局线程池
2.遇到的第二个问题:
1.换成全局线程池后,发现错误的范围蔓延了!因为网络接口非常不稳定,且效率过差,导致线程一直等待,然后线程池满了,导致其他服务线程池满报错!
思考:1.会不会是线程池太小了
行动:线程池扩大
3.遇到的第三个问题:
1.还是会存在线程池满的问题,对于当前的问题,有种无力的感觉,这个时候就在想:md,这是线程池和这个服务接口的问题,我代码一点问题没有,突然思路打开了,是不是代码可以优化一下,至少没那么难看,(▽)
**思考1.**为什么线程池报错导致500返回呢,按道理服务应该错误被吃掉,给个200然后返回为空得了,500错误解释不过去了
过程:review代码和日志的时候,发现就是es.submit 这一行爆的错,我想是不是这个错误没有catch 导致的,同时突然看到程序计数器 如果报错,是不是没有被减少! 那这会不会导致线程堵住??!
行动:果断的把try catch finally放在外面
思考2.:但是这个时候想,多个线程哎,这样写在外面合适嘛?
使用了大模型得到了回答,
为了让这个代码片段更加完整和正确,您应该包含一个 catch 块来处理任何可能抛出的异常。此外,虽然在这个特定的 submit 调用中直接捕获异常可能不是必需的(因为 submit 方法本身不会抛出检查型异常,并且会返回一个 Future 对象,任何异常都将被存储在该 Future 对象中),但在 lambda 表达式内部可能会抛出异常,这取决于您的代码逻辑。
行动:在线程中也加了try catch finally
思考3:咦,await是不是可以加个等待时间,这样再兜个底?
行动: 果断加上
结果:经过多次上线(该行为极其不建议,本人个人情况,需要批评的)
最终线程
1线程满的问题解决了
但是还是有个问题:1.线程满了,但是服务就像漏斗一样如果网络I/O一直很慢,最终还是会导致线程满掉。所以后期是给这个网络接口加个缓存!(其实写完这个博客,缓存已经加上了)只是没什么好讲的,就没在这篇文章中。
优化修改Bug: 第一层取消 finally 计数器减1,将其转到catch中
synListItem = Collections.synchronizedList(new ArrayList<>());
//创建一个CountDownLatch类,构造入参线程数
CountDownLatch countDownLatch = new CountDownLatch(listItem.size());
Iterator<Map<String, Object>> iterator = listItem.iterator();
final ExecutorService es = ExecutorFactory.getEs();
while (iterator.hasNext()) {
// 线程满导致异常,程序计数器未执行
try {
es.submit(() -> {
Map<String, Object> map = iterator.next();
// linkIds 为空表示开关未配置
try {
} catch (Exception e) {
e.printStackTrace();
} finally {
//该线程执行完毕-1
countDownLatch.countDown();
}
});
} catch (Exception e) {
countDownLatch.countDown();
e.printStackTrace();
}
}
try {
countDownLatch.await(200, TimeUnit.MILLISECONDS);
log.info("after menu onsale filter listItem size:{}", synListItem.size());
log.debug("after menu onsale filter listItem:{}", synListItem);
} catch (InterruptedException e) {
e.printStackTrace();
}
疑惑:1.双层try catch 真的好吗?
最终:
取消
catch (Exception e) {
// countDownLatch.countDown();
e.printStackTrace();
}
countDownLatch.await(200,TimeUnit.MILLISECONDS);
思考:1.如果加上,子线程还是在执行的过程中,如果对象没有深拷贝会出现多线程问题