返回首页
当前位置: 主页 > 网络编程 > Jsp实例教程 >

警惕hotspot1.6及之前ThreadPoolExecutor的bug

时间:2014-02-24 15:30来源:知行网www.zhixing123.cn 编辑:麦田守望者

近来阅读ThreadPoolExecutor的源码(jdk版本:1.6.0_26),发现有些问题。毕竟是出自并发大神Doug Lea之手,不敢有过多质疑,经测试确实出现了不合理之处,并在后续版本中有修正。

发现两个问题,其中有一个比较严重。先讲这个比较严重的。

在ThreadPoolExecutor$Worker的run方法的源码如下:

public void run() {
try {
Runnable task = firstTask;
firstTask = null;
while (task != null || (task = getTask()) != null) {
runTask(task);
task = null;
}
} finally {
workerDone(this);
}
}

线程池里的线程在while里不停的去workQueue里取任务并执行任务。当在执行任务的时候(runTask中最重要的事情就是调用了传入的task的run方法),如果task的run抛出异常,runTask方法并没有吞掉该异常,它会继续往外抛,这会导致退出ThreadPoolExecutor$Worker#run中的while循环,并最终进入到finally中的workerDone方法,workerDone方法做了什么呢?源码如下:
 

void workerDone(Worker w) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
completedTaskCount += w.completedTasks;
workers.remove(w);
if (--poolSize == 0)
tryTerminate();
} finally {
mainLock.unlock();
}
}

做了几件事:1、将该线程执行完成的任务数合并到线程池的总任务数中;2、移除该线程;3、该线程死后若池中没有线程了就调用tryTerminate方法,那么来看下tryTerminate:

private void tryTerminate() {
if (poolSize == 0) {
int state = runState;
if (state < STOP && !workQueue.isEmpty()) {
state = RUNNING; // disable termination check below
Thread t = addThread(null);
if (t != null)
t.start();
}
if (state == STOP || state == SHUTDOWN) {
runState = TERMINATED;
termination.signalAll();
terminated();
}
}
}

我们不去调用shutdown或者shutdownNow,如果池中没有线程了,就会走到if (state < STOP && !workQueue.isEmpty())中,这里往池里增加了一个线程。什么概念?重新理下思路,当线程池中的线程在执行任务时碰到异常,该线程会死掉,但只有当池中的线程数为0才会去创建一个线程。这意味着什么?举个例子:

有一个corePoolSize为5的线程池,其使用的队列是无界的LinkedBlockingQueue,用循环往该线程池提交10个任务(按编号0到9的顺序提交任务),每个任务的执行时间为5s,且编号0-4的任务在5s结束后会立马抛出一个异常,那么剩下的5个任务将由几个线程来执行?
按照上面源码中的逻辑,池中的5个线程在执行编号0-4的任务,当其中先执行完的4个任务抛出异常后,并不会去创建新的线程,只有等到最后一个任务结束(也就是最后一个线程死掉),才会去创建一个线程来执行队列里剩余的5个任务。来段代码测试下:

package com.ticmy.concurrency;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;

public class TestThreadPool {
private static final ExecutorService pool = Executors.newFixedThreadPool(5);

public static void main(String[] args) throws InterruptedException {
for(int i=0; i<10; i++) {
pool.execute(new MyTask(i));
}
for(int i=0; i<40; i++) {
System.out.println("poolSize:" + ((ThreadPoolExecutor)pool).getPoolSize());
Thread.sleep(1000L);
}
pool.shutdown();
}

static class MyTask implements Runnable {
private int num;
public MyTask(int index) {
this.num = index;
}
public void run() {
try {
Thread.sleep(5000L);
System.out.println("task " + num + " executed by " + Thread.currentThread());
} catch (InterruptedException e) {
e.printStackTrace();
}
if(num < 5) {
throw new RuntimeException("task " + num + ",executed by " + Thread.currentThread());
}
}
}
}

在hotspot1.5、1.6下执行会发现,前5个抛出异常后,池中的线程数只有1了,后面的队列里的5个任务完全是串行执行的。

------分隔线----------------------------
标签(Tag):Java JAVA实例教程 JAVA基础教程 Java源代码 Java技巧
------分隔线----------------------------
推荐内容
猜你感兴趣