使用阻塞队列批量导入与使用forkjoinPool框架的导入对比

一:本人使用的环境

jdk1.7+window 10 + oracle 11g

二:使用的技术

1:阻塞队列(BlockingQueue):先进先出(FIFO),生产者-消费者的模式

分为有界队列(ArrayBlockingQueue)、无界队列(linkedBlocingQueue)等,其他的不在此举例

其put()方法:如果队列已满则阻塞,直到队列中减少,才能被唤醒其线程,有机会去争取资源,继续添加元素到队列中。

其take()方法:如果队列中没有元素,则阻塞该线程,直到阻塞队列中put元素到队列中,才能被唤醒线程。

2:使用ForkJoinPool框架:一种将大任务分割成若干的小任务来执行,至于分成多少个,设置其阀值,比如阀值为100,如果小于100,则不分割,超过100,就再次的分          割,其一个重要的特点(也是其框架的一种有效的智能方法来平衡可用线程的工作负载):工作密取(working-stealing),当某个线程的所有任务都现行执行完毕时,就会向其他执行慢的任务队列中偷取任务来完成,从队列的尾部来偷取,(该队列使用的是双端队列),正常情况下都是从队列头部来取任务来执行,只有工作窃取的时候才会从队列的尾部来取任务,需要继承一个计算合并结果的RecursiveTask或一个没有任务返回的RecursiveAction。其中invoke(ForkJoinTask)同步/有返回结果、execute()异步/没有返回任何。submit(ForkJoinTask)异步/返回一个ForkJoinTask

其缺点是:因为产生大量的子任务,结束后,GC的时长会长,大概1s多。


3:使用线程池(ThreadPoolExecutor):可以很大的程度上减少传统的新建(new Thread)以及销毁而带来的性能降低和内存资源消耗的开销。(接口ExecutorService可以执行线程)

1.newFixedThreadPool(int)可以指定固定的线程数,操作无界队列

2.newScheduleThreadPool(int corePoolsize):可以调度一个在给定的延迟后运行。

3.newSingleThreadPool():创建一个单一的线程来执行。

4.newCacheThreadPool():根据需要来创建新的线程,但在可用时将重用先前的构建的线程。

三:使用阻塞队列时:

ExecutorService executorService = Executors.newFixedThreadPool(10);

BlockingQueue queue = new ArrayBlockingQueue(10000);
BlockQueuThread aa = new BlockQueuThread(queue, randomCodeSeg);
executorService.execute(aa);

SegCodeQueueThread codeQueue = null;
for(int i =0 ; i<4;i++){
codeQueue  = new SegCodeQueueThread(baseCdsService,codeSegmentDao, codePoolBean, queue);
executorService.execute(codeQueue);
}

BlockQueuThread 读入到队列中的run方法体:

int siez = randomCodeSeg.size();
String aa = "end";
try {
for(int i = 0 ; i< siez ; i++ ){
queue.put(randomCodeSeg.get(i));
System.out.println("线程name:"+Thread.currentThread().getName()+"-->该线程状态:"+Thread.currentThread().getState()+"queue队列长度:"+queue.size());
}
queue.put(aa);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
System.out.println(e.getMessage());
e.printStackTrace();


SegCodeQueueThread中的run方法体:

boolean bo = false;
// final LinkedList list = new LinkedList<>();
while(!bo){
final String take = queue.take();
final Object[] ob = new Object[2];
ob[0] = codePoolBean.getSEGMENT_ID();
ob[1] = take;
// list.add(ob);
if("end".equals(take)){
System.out.println("结束blockingQueue");
bo = true;
continue;
}
System.out.println("线程name:"+Thread.currentThread().getName()+"-->该线程状态:"+Thread.currentThread().getState()+"queue取出:"+take);
// codeSegmentDao.insertCodeSegNumT(take,codePoolBean);
baseCdsService.update(sql, ob);
}


执行92万条数据,纯插入单张表,但是执行到一定的时候,线程发生死锁;锁死的错误如下:

 堆栈跟踪: 
java.io 

.FileOutputStream.writeBytes(Native Method)
java.io 

.FileOutputStream.write(FileOutputStream.java:318)
java.io 

.BufferedOutputStream.flushBuffer(BufferedOutputStream.java:82)
java.io 

.BufferedOutputStream.flush(BufferedOutputStream.java:140)
   - 已锁定java.io 

.BufferedOutputStream@fe5ea3e
java.io 

.PrintStream.write(PrintStream.java:482)
   - 已锁定java.io 

.PrintStream@40ee52e3
sun.nio.cs.StreamEncoder.writeBytes(StreamEncoder.java:221)
sun.nio.cs.StreamEncoder.implFlushBuffer(StreamEncoder.java:291)
sun.nio.cs.StreamEncoder.flushBuffer(StreamEncoder.java:104)
   - 已锁定java.io 

.OutputStreamWriter@3f759499
java.io 

.OutputStreamWriter.flushBuffer(OutputStreamWriter.java:185)
java.io 

.PrintStream.write(PrintStream.java:527)
   - 已锁定java.io 

.PrintStream@40ee52e3
java.io 

.PrintStream.print(PrintStream.java:669)
java.io 

.PrintStream.println(PrintStream.java:806)
   - 已锁定java.io 

.PrintStream@40ee52e3
org.apache.tomcat.util.log.SystemLogHandler.println(SystemLogHandler.java:264)
com.uniform.codeSegment.util.SegCodeQueueThread.run(SegCodeQueueThread.java:52)
java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
java.lang.Thread.run(Thread.java:722)

问题定位:从上面已锁定可以看出,在操作IO的时候出现死锁,在网上搜索下, 原因是system.out.print()的问题,在多线程使用的情况下, 出现这种情况,

有使用log4j,也会log。info()出现问题。

问题解决:将所有执行的操作中去掉system打印语句。




可参考文章:http://blog.csdn.net/qq_24504453/article/details/75028213



使用forkJoinPool框架的时候,同样的批量导入操作,经本人多次的测试,没有发生异常,每秒达到680次。

继承两种方式:一种有返回值的,合并所有的子任务后返回数据(RecursiveTask),还有一种没有返回数据的

(RecursiveAction)

代码如下:

ForkJoinPool forkJoinPool = new ForkJoinPool();
SegCodeTask codeTask = new SegCodeTask(baseCdsService,codeSegmentDao, codePoolBean, randomCodeSeg, 0, randomCodeSeg.size());
// forkJoinPool.execute(codeTask);
Integer invoke = forkJoinPool.invoke(codeTask);
System.out.println("获取已启动但是还未执行完成的线程数目:"+forkJoinPool.getPoolSize());
System.out.println("获取由工作线程队列中的任务总数的计算:"+forkJoinPool.getQueuedTaskCount());
System.out.println("获取从一个线程被另一个线程窃取的总任务数:"+forkJoinPool.getStealCount());
System.out.println("获取活动的线程数目:"+forkJoinPool.getActiveThreadCount());
System.out.println("总任务执行数目:"+invoke);


@Override
protected Integer compute() {
if(end - begin <= mid){
LinkedList list = new LinkedList<>();
try{
long startTime=System.currentTimeMillis();
for(int i= begin ; iObject[] ob = new Object[2];
ob[0] = codePoolBean.getSEGMENT_ID();
ob[1] = randomCodeSeg.get(i);
list.add(ob);
// String take = randomCodeSeg.get(i);
// codeSegmentDao.insertCodeSegNumT(take,codePoolBean);
}
baseCdsService.batchInsert(sql, list);
// codeSegmentDao.insertCodeSeg(list,codePoolBean);
long endTime=System.currentTimeMillis();
float excTime=(float)(endTime-startTime)/1000;
System.out.println("执行sql语句的时间:"+excTime+"s");

}catch (Exception e) {
// TODO: handle exception
e.getStackTrace();
return 0;
}
return list.size();
}else{
int middle = (end + begin) / 2;
SegCodeTask oneTask = new SegCodeTask(baseCdsService,codeSegmentDao, codePoolBean, randomCodeSeg,begin,middle);
SegCodeTask twoTask = new SegCodeTask(baseCdsService,codeSegmentDao, codePoolBean, randomCodeSeg, middle, end);
invokeAll(oneTask,twoTask);
return oneTask.join() + twoTask.join();
}


特别注意,上述的操作都是使用SPRING 中的jdbc,使用批量插入batchInsert以及单条插入

但是使用mybatis中sqlSession交互数据库,却发现92万根本无法导入成功,都会在几万的时候会锁死,也使用过动态sql的foreach作导入,也不尽人意

不知道是否是mybatis就不支持大批量的导入操作呢?还是sqlsession连接数问题?

你可能感兴趣的:(多线程类)