对于java.util.concurrent.Executors所提供的FixedThreadPool,可以保证可以在内存中有固定数量的线程数运行。但是由于FixedThreadPool绑定的是LinkedBlockingQueue。队列的上限没有限制(默认上限为Integer.MAX_VALUE),不断的提交新的线程,会造成任务在内存中长时间的堆积。
我们有可能面临如下的场景,主线程不断地提交任务线程,希望有固定数量的在线程中运行,也不想造成线程在内存中大量的等待堆积。由此需要我们自己定义一个线程池策略。ThreadPoolExecutor为我们线程池的设置提供了很大的灵活性。
首先看FixedThreadPool的实现:
public static ExecutorService More ...newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue(),
threadFactory);
}
可以看到,FixedThreadPool绑定的是LinkedBlockingQueue。
①我们需要做的第一个改造就是绑定有大小上线的BlockingQueue,在我的实现中绑定ArrayBlockingQueue并设置size。
②第二个是采用CallerRunsPolicy。ThreadPoolExecutor可以定义不同的任务拒绝策略。CallerRunsPolicy指的是当线程池拒绝该任务的时候,线程在本地线程直接execute。这样就限制了本地线程的循环提交流程。
BlockingQueue workingQueue = new ArrayBlockingQueue(10);
RejectedExecutionHandler rejectedExecutionHandler = new ThreadPoolExecutor.CallerRunsPolicy();
ExecutorService threadPool = new ThreadPoolExecutor(10, 10, 0L, TimeUnit.MILLISECONDS,workingQueue, rejectedExecutionHandler);
for (int i = 0; i < 100; i++) {
threadPool.submit(new Callable() {
@Override
public Boolean call() throws Exception {
System.out.println("thread " + String.valueOf(threadNo) + " is called");
Thread.sleep(10000);
System.out.println("thread " + String.valueOf(threadNo) + " is awake");
throw new Exception();
}
});
}
代码中定义了大小为10的线程池,for循环提交了20个线程的时候,10个执行线程,10个线程放入了workingQueue。当提交到第21个线程的时候,会触发RejectedExecutionHandler。在这里我们配置了CallerRunsPolicy策略。所以会在主线程直接执行该线程。也就是说,在本程序中最多会有11个线程在执行,10个线程在等待。由此限制了线程池的等待线程数与执行线程数
例子:向数据库中批量插入数据(共插入一千二百万条数据,每800条插入一次 )
public class InserMerchantDatetTask implements Runnable{
List list;
public InserMerchantDatetTask(List list){
this.list = list;
}
@Override
public void run() {
differenceService.batchInsertMerchantDate(list);
}
}
//线程等待策略:线程池里创建三个线程,当三个线程用完后,第四个线程到达时在主线程执行,所以最多有四个线程在执行,三个线程在等待
//这样主线程里就不会创造更多的线程以免主线程不停地创建新的线程造成等待线程过多引起等待超时
BlockingQueue workingQueue = new ArrayBlockingQueue(3);
RejectedExecutionHandler rejectedExecutionHandler = new ThreadPoolExecutor.CallerRunsPolicy();
ExecutorService exec = new ThreadPoolExecutor(3, 3, 0L, TimeUnit.MILLISECONDS,workingQueue, rejectedExecutionHandler);
/**
* 插入商家的Excel表数据
* @throws IOException
*/
@RequestMapping(value="/merchant/insertDate")
public String insertMerchantDate() throws IOException{
//获取相应文件夹下的所有csv文件
List files = FileUtil.getFiles("E:\\newwork\\merchant\\ing");
//循环遍历所欲csv文件并且读取数据
for(File f : files){
File inFile = new File("E:\\newwork\\merchant\\ing\\"+f.getName());
String inString = "";
int i=0;
try {
List list = new ArrayList(800);
//读取文件里的数据
BufferedReader reader=new BufferedReader(new InputStreamReader(new FileInputStream(inFile),"GBK"));
while((inString = reader.readLine())!= null){
i++;
//读取的每行数据是用","隔开的
String[] split = inString.split(",");
//i!=1 表示不读取第一行数据,因为第一行为标题
if(i!=1){
Merchant merchant = new Merchant();
merchant.setOrderId(split[0].replaceAll("\"", ""));
merchant.setMerchantOrder(split[1].replaceAll("\"", ""));
merchant.setTele(split[2].replaceAll("\"", ""));
merchant.setAmount(split[3].replaceAll("\"", ""));
merchant.setSaleAmount(split[4].replaceAll("\"", ""));
merchant.setOrderTime(split2[0]);
merchant.setCompletionTime(split3[0]);
merchant.setOperator(split[7].replaceAll("\"", ""));
merchant.setOrderStatus(split[8].replaceAll("\"", ""));
list.add(merchant);
if(i%800==0){
//当满800条数据时将list进行批量插入
exec.execute(new InserMerchantDatetTask(list));
list = new ArrayList(800);
}
}
}
//最后一次读到了数据并且不满800条时执行插入
if(list.size()!=0){
differenceService.batchInsertMerchantDate(list);
list = null;
}
reader.close();
} catch (FileNotFoundException ex) {
System.out.println("没找到文件!");
} catch (IOException ex) {
System.out.println("读写文件出错!");
}
}
return "redirect:/index.jsp";
}