JAVA多线程获取服务器数据实战
java线程池的几种创建方式
Java通过Executors提供四种线程池,分别为:
- newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
- newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
- newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
- newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行
这里以newFixedThreadPool为例,结合网上资料实现过程如下:
定义实体类
用来初始化线程和返回相关数据
@Slf4j
@Data
public class ThreadCallService implements Callable<Object> {
// 查询参数
private Map<String, Object> param;
private String key;
// 调用的service地址
private String serviceAddress;
public static int count = 0;
private long consumeTime;
// 返回的结果
private Object resultData;
public ThreadCallService (String serviceAddress, Map<String, Object> paramMap) {
// 初始化参数
this.param = paramMap;
this.serviceAddress = serviceAddress;
}
@Override
public Object call(){
count ++;
// 调用service获取相关数据
log.info("开始线程 " + count);
// 这里是调用服务器对外开放的API获取数据(具体形式具体对待)
resultData = ServiceUtil.connectService(serviceAddress, "apiUrl", param);
// 返回数据给Future
return resultData;
}
}
主方法
初始化所有线程–> 启动线程 --> 获取结果
private List<Map> multiThreadHandleData(List<Map<String, Object>> paramMapList, String key)
throws Exception {
List data = new ArrayList<>();
// 多线程查询方式
long start = System.currentTimeMillis();
List<Callable<Object>> threadList = new ArrayList<>();
Callable<Object> callable = null;
for (Map<String, Object> paramMap : paramMapList) {
// 初始化线程
callable = new ThreadCallService (webServiceAddress, paramMap);
threadList.add(callable);
}
// 初始化线程数量,防止卡死
ExecutorService executorService = Executors.newFixedThreadPool(paramMapList.size());
//Future用于获取结果
List<Future<Object>> futures = executorService.invokeAll(threadList);
//处理子线程返回结果
if (CollectionUtils.isNotEmpty(futures)) {
for (Future<Object> future : futures) {
// 具体数据结构具体处理
data.add(future.get());
}
log.info("此次相关 " + key + " 的" + paramMapList.size() + " 个线程都跑完了,继续主线程的工作");
// 关闭线程池
executorService.shutdown();
while (true) {
// 判断线程池中任务是否全部执行完毕 若执行完毕 再返回 list
if (executorService.isTerminated()) {
break;
}
}
long end = System.currentTimeMillis();
log.info("线程查询 " + key + " 数据用时: " + (end - start) + "ms");
log.info("下个数据类型的线程,起点编号为 : " + (ThreadCallWebservice.count + 1));
}
return data;
}
注意 : 这里的paramMapList的大小直接决定的线程池的大小,实际可以通过评估服务器当前用户的最大线程数和压力来指定大小
总结
- 线程池的初始化方式需要从硬件设施/业务场景综合考虑
- 以单个线程要做的事情为单元来封装实体类可以使程序变得更灵活通用
- 用简洁的日志来直观的显示线程的执行过程