以前知道线程,也只是继承Thread或者实现Runnable接口,只是基本的皮毛。后来看了Executor框架,及其内部实现ThreadPoolExecutor,大体知道怎么回事,但是怎么用在实际的项目里,还不是没有什么头绪!后来看了《
java Web项目中什么场景中会用到java多线程?
》,大体有点明白,耗时的批量任务都可考虑用多线程优化!
个人总结:执行效率特别低,耗时特别长都可以考虑多线程。如果耗时操作本身就包含多个task,可以直接提交到线程池去执行 ;如果是很多次的循环,每个任务(task)的界限不是很明确,可以像上面“场景一”中“验证1万条url路径是否存在”的解决思路,人为划分成多个任务(比如把处理200条url作为一个任务),分别提交到线程池去执行。划分任务 --> 提交任务到线程队列
结合自己的项目,觉得把数据批量导入到数据库,比较适合用多线程优化!先简单写了个多线程模拟导入过程的列子,后来就应用实践项目 中。做完这些突然觉得下面这句话很有道理,
生活中从不缺少美,而是缺少发现美的眼睛——罗丹
实际场景需求:点击批量导入按钮,选择待导入文件,然后开始导入。
导入需要耗时,为了增加友好性,导入过程中显示导入进度。
待导入完成,显示导入成功的数据条数。
如果不需要监控测试导入进度,代码如下:
package com.lyl.thread.batchImport;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 模拟数据导入,不需要关注导入结果
*/
public class BatchImportThread {
public int dataSize = 50;
public void doImport(){
try {
//模拟导入订单数据
final List<Integer> dataList = this.getDataList();
long startTime = System.currentTimeMillis();
for(int i = 0; i < dataSize; i++){
//模拟做一条数据导入到数据库花费的时间
Thread.sleep(30);
System.out.println(dataList.get(i));
}
System.out.println("普通循环=======" + (System.currentTimeMillis() - startTime));
} catch (Exception e) {
e.printStackTrace();
}
}
//模拟导入数据
public List<Integer> getDataList(){
//模拟导入数据
final List<Integer> dataList = new ArrayList<Integer>();
for(int i = 0; i < dataSize; i++){
dataList.add(i);
}
return dataList;
}
public void doImportByThread(){
try {
//模拟导入订单数据
final List<Integer> dataList = this.getDataList();
//拆分成多个任务,每个任务包含step条数据
int step = 10;
int totalTasks = (dataSize % step == 0 ? dataSize/step : (dataSize/step + 1));
System.out.println(totalTasks);
final CountDownLatch countDownLatch = new CountDownLatch(totalTasks);
long startTime1 = System.currentTimeMillis();
//线程数量对程序耗时有不同影响
// ExecutorService executorService = Executors.newFixedThreadPool(15);
ExecutorService executorService = Executors.newCachedThreadPool();
for(int j = 0; j < dataSize; j=j+step){
final int start = j;
final int perCount = (dataSize - start) < step ? (dataSize - start) : step;
System.out.println(perCount);
executorService.execute(new Runnable() {
public void run() {
try {
System.out.println(" start == " + start + " , count" + perCount);
for(int n = 0; n < perCount; n++){
System.out.println(dataList.get(start + n));
//模拟做一条数据导入到数据库花费的时间
Thread.sleep(30);
}
countDownLatch.countDown();
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
countDownLatch.await();
System.out.println("线程池循环耗时=======" + (System.currentTimeMillis() - startTime1));
executorService.shutdown();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
BatchImportThread batchImportThread = new BatchImportThread();
batchImportThread.doImport();
batchImportThread.doImportByThread();
}
}
多线程模拟实际场景需求的代码:
package com.lyl.thread.batchImport;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 模拟订单导入并显示进度,spring 中此类是一个单例,多个用户同时导入订单,会同时访问此类。
* 实际web项目中,把成功导入的订单数(tradeId,successCntMap.get(tradeId))放到redis,
* jsp页面在发出导入请求的时候,开启一个setTime函数根据tradeId每隔一秒向服务器请求导入成功的订单数,
* 等doImport方法结束,导入请求结束,jsp页面显示服务器返回的导入成功的订单数,并取消setTime函数
*/
public class BatchImportThreadProgress {
//是否结束
private volatile boolean isStop = false;
//多个导入请求会同时访问此类,防止多个导入互相更改导入成功订单数量,所以设置为Map类型,
// 使一个tradeId对应一个导入成功订单数量,tradeId对应successCntMap中的key,表示本次请求导入成功的数量
private volatile Map<String, Integer> successCntMap = new HashMap<String, Integer>();
/**
* 模拟导入
* @param tradeId
*/
public void doImport(final String tradeId){
try {
final List<Integer> dataList = new ArrayList<Integer>();
//模拟导入订单
int dataSize = 9;
for(int i = 0; i < dataSize; i++){
dataList.add(i);
}
//初始化
successCntMap.put(tradeId, 0);
//多线程导入
ExecutorService executorService = Executors.newFixedThreadPool(9);
int step = 2;
int cysSum = (dataSize % step == 0 ? dataSize/step : (dataSize/step + 1));
// System.out.println("循环次数===" + cysSum);
final CountDownLatch countDownLatch = new CountDownLatch(cysSum);
long startTime1 = System.currentTimeMillis();
for(int j = 0; j < dataSize; j=j+step){
final int start = j;
final int perCount = (dataSize - start) < step ? (dataSize - start) : step;
// logger.info("n==" + n);
executorService.execute(new Runnable() {
public void run() {
try {
// System.out.println(new StringBuilder(tradeId).append(" === start == ").append(start).append(" , count").append(perCount).toString());
for(int n = 0; n < perCount; n++){
System.out.println(new StringBuilder(tradeId).append(" === 导入数据 == ").append(dataList.get(start + n)).toString());
Thread.sleep(300);
synchronized (tradeId){
successCntMap.put(tradeId, successCntMap.get(tradeId) + 1);
}
//把导入成功的订单数记录到Redis中()
//redis.put(tradeId, successCntMap.get(tradeId));
}
countDownLatch.countDown();
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
//等所有线程都结束,才返回结果
countDownLatch.await();
System.out.println(new StringBuilder(tradeId).append(" == 线程池循环耗时 === ").append((System.currentTimeMillis() - startTime1))
.append(" , 成功导入数据条数:").append(successCntMap.get(tradeId)).toString());
executorService.shutdown();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException {
final BatchImportThreadProgress batchImportThreadProgress = new BatchImportThreadProgress();
//模拟多个(count)用户,同时导入订单
int count = 2;
final CountDownLatch countDownLatch = new CountDownLatch(count);
for(int i = 0; i < count; i++){
final String tradeId = "tradeId"+i;
new Thread(new Runnable() {
public void run() {
batchImportThreadProgress.doImport(tradeId);
countDownLatch.countDown();
}
}).start();
}
//获得导入进度 - -web项目中不需要这样监控,需要单独写一个接口,jsp页面根据tradeId每隔一秒(settime函数)请求一次该接口,
// 接口从redis里缓存的进度返回给jsp,并刷新结果
new Thread(new Runnable() {
public void run() {
System.out.println("-------获得导入进度--------");
while (!batchImportThreadProgress.isStop){
try {
String tradeId = "";
for(int i = 0; i < count; i++){
tradeId = "tradeId"+i;
System.out.println("---batchImportThreadProgress---" + tradeId + "---获得导入进度--------" + batchImportThreadProgress.successCntMap.get(tradeId));
}
Thread.sleep(500);//每隔0.5秒显示一次进度
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
//等待线程池结束
countDownLatch.await();
batchImportThreadProgress.isStop = true;
}
}