一、背景
最近在工作中需要调用其他服务的接口,这个接口限制只能单次调用,而我这边服务的需要调用几百次,emm...首先想到的肯定是循环调用,然后把每次返回的结果存到数组中,最后就得到了完整的数据。但是问题也随之而来,循环调用700次整个过程大概耗时5分钟左右。这个时长显然是无法让人接受的。于是想到了多线程调用来提高效率。
二、解决方案
首先创建一个线程池,然后循环遍历需要调用的业务,使用submit方法把任务丢进池子中。
private static final ThreadPoolExecutor executor =
new ThreadPoolExecutor(20, 25, 60L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(),
new ThreadPoolExecutor.CallerRunsPolicy());
//用于存放请求接口的数据
ArrayList<Object> appUrlInfos = new ArrayList<>();
//计数器等待线程任务执行完,主线程才执行
CountDownLatch count = new CountDownLatch(userdata.size());
for (Object userdataUrl : userdata) {
executor.submit(() -> {
//构造请求体
try {
//请求接口 部分代码省略
String response = OkHttpUtils.builder().url(requestUrl)
.post(req_body.toJSONString()).sync();
appUrlInfos.add(JSONObject.parseObject(response));
} catch (Exception e) {
e.printStackTrace();
} finally {
count.countDown();
}
});
}
count.await();
写完之后跑了一下发现新的问题又出现了,调了800次接口,数组应该800个元素才对,但是结果只有794个,这六个难道是多线程执行的过程中任务丢了?但是也没有道理呀,因为使用的是LinkedBlockingQueue,队列长度是默认的是Integer.Max,并不会触发线程池的拒绝策略。
百思不得其解,无奈之下只好打断点去逐步排查,最终发现多线程并没有丢任务,每个任务都被执行了,那数组的长度不够,会不会是添加这一行代码出了问题呢?想到这里看了看数组果然,ArrayList是一个现场不安全的数组,在高并发的情况下,可能会出现多个线程同时对数组添加了元素,但只有一个生效了,所以导致这样的问题发生。找到问题所在那就很好解决了,换一个线程安全的数组就可以啦用Vector代替或者使用Collections包装类中的synchronizedList方法。最终使用了Collections.synchronizedList(appUrlInfos)就解决了这个问题。
重要的事情说三遍!!!
只要使用到了多线程就一定要考虑线程安全问题
只要使用到了多线程就一定要考虑线程安全问题
只要使用到了多线程就一定要考虑线程安全问题