背景
我们在开发中常常会遇到针对某个服务出现响应时间过长,用户使用体验感极差,出现这种问题的原因比较多,我们现在就说一下其中的一种情况,for循环遍历查询。
前提
首先先来简单介绍一个多线程,提到多线程,首先要明白这几个词汇的意思,进程,线程,并发,并行,多线程,同步。
进程:简单来讲,就是一个正在进行中的程序,比如请求一次接口。
线程:进程中独立运行的一个子任务就是一个线程,比如请求一次接口的过程就可以是一个线程,也可以启动多个线程。
并发:指两个或多个事件在同一时间段内执行。
并行:值两个或多个时间在同一时间点执行。
同步:通过人为的控制使多线程访问公共资源时保证线程安全,比如在公共方法上加锁(synchronized),线程安全是优化线程性能的前提。
优化前代码
JSONArray devices = req.getJSONArray("devices");
if(null!=devices&&!devices.isEmpty()){
for(Object device:devices){
saveTodayAICalculate(device); //循环执行方法
}
}
//方法
public void saveTodayAICalculate(JSONObject device){
//执行方法内容
xxx...
}
优化后代码
JSONArray devices = req.getJSONArray("devices");
if(null!=devices&&!devices.isEmpty()){
try{
ExecutorService es = Executors.newFixedThreadPool(8); //定义线程池
CountDownLatch cdld = new CountDownLatch(devices.size());
for(Object device:devices){
es.execute(new Runnable(){
@Override
public void run() {
saveTodayAICalculate(device,cdld); //执行线程方法
}
});
}
cdld.await();
}catch(Exception e){
logger.error("execute executorServcice error,reason is {}",e);
}finally{
es.shutdown(); //执行完一定要销毁线程,不然容易出现内存溢出异常
}
}
//线程执行方法
public void saveTodayAICalculate(JSONObject device,CountDownLatch cdl){
try{
xxx...
}catch(Exception e){
xxx...
}finally{
if(null!=cdl){
cdl.countDown();
}
}
}
说明
1 通过java.util.cocarrent.Executors创建线程池ExecutorService,创建的方式有多种,这里只使用newFixedThreadPool穿件固定大小线程池。
2 使用countDownLatch控制线程的执行。
3 多线程执行完一定要销毁线程池,不然会出现内存溢出异常。
总结一下,这种多线程的缺陷一是当并发量多时,容易创建大量的线程池,占用大量cpu和内存,二是当执行线程需要返回数据时不满足,这种情况可以通过实现Callable接口,然后通过Future获取返回值。比如:
ExecutorService es = Executors.newFixedThreadPool(8);
List<Future<String>> tasks = new ArrayList<>();
for(int i=0;i<12;i++){
Future<String> future = es.submit(new MyCallable(i));
tasks.add(future);
}
for(Future<String> future : tasks){
System.out.println(future.get());
}
//线程类
public class MyCallable implements Callable<String> {
private Integer k;
public MyCallable(Integer k){
this.k = k;
}
@Override
public String call() throws Exception {
return this.k +":"+ this.toString();
}
}