一个 java web 多线程,线程池实际案例
要求:根据接口的内容,同时运行多个sql查询,并且把返回的数据整合起来。
框架spring mvc,mybatis
单线程的代码
MyThread myThread=new MyThread();
@PostMapping("/sql")
@ApiOperation(value="并发sql")
public List<JSONObject> sql(@RequestBody JSONObject stringList)
{
long start=System.currentTimeMillis(); //获取开始时间
//建立线程安全的List存储多线程返回的数据
ArrayList<JSONObject> jsonObjectList=new ArrayList();
stringList.forEach((key,val)->{
if (!(val + "").isEmpty()) {
//通过工厂执行查询(有参)
jsonObjectList.addAll(Factory.getInvokeStrategy(key).Sql(JSON.parseObject(val + "")));
} else {
//通过工厂执行查询(无参)
jsonObjectList.addAll(Factory.getInvokeStrategy(key).Sql(new JSONObject()));
}
});
long end=System.currentTimeMillis(); //获取结束时间
System.out.println("程序运行时间: "+(end-start)+"ms");
return jsonObjectList;
}
请求体{"xxxx":"xxxxx","xxx":"xxx","xxxx":"xxx"}(参数内容不是重点不做赘述)
同时运行3条sql时的时间
程序运行时间: 268ms
多线程代码
MyThread myThread=new MyThread();
@PostMapping("/sql")
@ApiOperation(value="并发sql")
public List<JSONObject> sql(@RequestBody JSONObject stringList)
{
long start=System.currentTimeMillis(); //获取开始时间
//建立线程安全的List存储多线程返回的数据
CopyOnWriteArrayList<JSONObject> jsonObjectList=new CopyOnWriteArrayList();
CountDownLatch countDownLatch=new CountDownLatch(stringList.size());
stringList.forEach((key,val)->{
myThread.addThread(new Thread(() -> {
if (!(val + "").isEmpty()) {
//通过工厂执行查询(有参)
jsonObjectList.addAll(Factory.getInvokeStrategy(key).Sql(JSON.parseObject(val + "")));
countDownLatch.countDown();
} else {
//通过工厂执行查询(无参)
jsonObjectList.addAll(Factory.getInvokeStrategy(key).Sql(new JSONObject()));
countDownLatch.countDown();
}
}));
});
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
long end=System.currentTimeMillis(); //获取结束时间
System.out.println("程序运行时间: "+(end-start)+"ms");
return jsonObjectList;
}
程序运行时间: 93ms
速度提升三倍,
下面来讲一下上面的多线程代码
myThread是我自己随便封装的线程池
public class MyThread {
private ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 10, 100, TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<Runnable>(500));
public void addThread(Thread thread) {
threadPoolExecutor.execute(thread);
}
public int getActiveCount (){
return threadPoolExecutor.getActiveCount() ;
}
/**
* 结束线程池(不能向线程池中添加新的线程)
*/
public void end(){
threadPoolExecutor.shutdown();
}
public boolean isEnd(){
return threadPoolExecutor.isTerminated();
}
}
既然是多线程,那原本的用于存储返回值的list由于线程不安全,不可以使用。这里可以用java提供的
CopyOnWriteArrayList
继承了list同时又是线程安全的。
CopyOnWriteArrayList<JSONObject> jsonObjectList=new CopyOnWriteArrayList();
利用线程池新建和管理子线程。(中间的业务代码,是标准的利用Spring自动注册的工厂,不清楚的可以自行去了解)
stringList.forEach((key,val)->{
myThread.addThread(new Thread(() -> {
if (!(val + "").isEmpty()) {
//通过工厂执行查询(有参)
jsonObjectList.addAll(Factory.getInvokeStrategy(key).Sql(JSON.parseObject(val + "")));
countDownLatch.countDown();
} else {
//通过工厂执行查询(无参)
jsonObjectList.addAll(Factory.getInvokeStrategy(key).Sql(new JSONObject()));
countDownLatch.countDown();
}
}));
});
子线程在线程池的管理下自动执行,但此时的主线程,也在运行。由于主线程不需访问数据库,主线程会先于子线程把数据传出去,此时传出的数据就会是空值。
因此我们需要,让主线程停下来等待子线程执行完毕。
阻塞主线程的办法有很多,如添加while,join,或者用CountDownLatch计数,
额外注意一下,由于使用了线程池,子线程可能不会自动结束,因此利用join阻塞主线程的方法在这里无法使用。
这里我使用CountDownLatch
CountDownLatch countDownLatch=new CountDownLatch(stringList.size());
myThread.addThread(new Thread(() -> {
if (!(val + "").isEmpty()) {
//通过工厂执行查询(有参)
jsonObjectList.addAll(Factory.getInvokeStrategy(key).Sql(JSON.parseObject(val + "")));
countDownLatch.countDown();
} else {
//通过工厂执行查询(无参)
jsonObjectList.addAll(Factory.getInvokeStrategy(key).Sql(new JSONObject()));
countDownLatch.countDown();
}
}));
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
到这里,代码就结束了。
个人认为这是一种很优雅的写法,尽可能的简化了代码,(使用原生的lock锁简直是让人抓狂)