线程池使用FutureTask时如果拒绝策略设置为DiscardPolicy和DiscardOldestPolicy,并且调用被拒绝的任务的get()方法,那么调用线程会一直被阻塞。
案例分析
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建线程池,线程大小为1,队列大小为1,拒绝策略使用DiscardPolicy
ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(1), Executors.defaultThreadFactory(), new ThreadPoolExecutor.DiscardPolicy());
List<Future> futureList = new ArrayList<>();
for (int i = 0; i < 3; i++) {
//执行任务
Future<Object> future = executor.submit(new Callable<Object>() {
@Override
public Object call() throws Exception {
System.out.println(Thread.currentThread().getName() + " start");
Thread.sleep(1000L);
System.out.println(Thread.currentThread().getName() + " end");
return null;
}
});
futureList.add(future);
}
//等待线程执行完毕
for (Future future : futureList) {
future.get();
}
//关闭线程池
executor.shutdown();
}
执行结果
打开Java监视和管理控制台可以看到pool-1-thread-1这个线程仍然存活,也就是说shutdown并没有起到作用
问题分析
JAVA线程池的理解这篇文章讲到,当阻塞队列已满且当前前线程数大于最大线程数时会执行拒绝策略,这里使用的DiscardPolicy拒绝策略,首先看看此拒绝策略做了什么
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}
从代码可以看出,DiscardPolicy拒绝策略什么也没做,直接丢弃任务
然后我们再来看看submit(Callable task)方法做了什么
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
//装饰Runnable为Future对象
RunnableFuture<T> ftask = newTaskFor(task);
execute(ftask);
return ftask;
}
newTaskFor(Callable callable)
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
return new FutureTask<T>(callable);
}
FutureTask构造函数
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
//设置state为NEW **关键点**
this.state = NEW; // ensure visibility of callable
}
execute(Runnable command)
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
//线程数小于核心线程数则新增线程处理
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
//线程数已达到核心线程数则加入阻塞队列
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
//尝试新增线程处理
else if (!addWorker(command, false))
//执行拒绝策略
reject(command);
}
submit(Callable task)主要是生成一个FutureTask对象,此时FutureTask对象的state状态值为NEW,然后执行线程池的execute方法,如果线程数小于核心线程数则新增线程处理,线程数已达到核心线程数则加入阻塞队列,如果队列已满且线程数大于最大线程数则执行拒绝策略。
由上文可知,此处使用DiscardPolicy拒绝策略,什么也不做,直接丢弃任务,所以当执行拒绝策略时返回了一个状态值为NEW的FutureTask对象。那么,调用Future的get()方法什么情况下才会返回呢?
public V get() throws InterruptedException, ExecutionException {
int s = state;
//状态值小于COMPLETING时等待,否则调用report方法返回
if (s <= COMPLETING)
s = awaitDone(false, 0L);
return report(s);
}
FutureTask状态值
private volatile int state;
private static final int NEW = 0;
private static final int COMPLETING = 1;
private static final int NORMAL = 2;
private static final int EXCEPTIONAL = 3;
private static final int CANCELLED = 4;
private static final int INTERRUPTING = 5;
private static final int INTERRUPTED = 6;
可以看到,只有当状态值大于COMPLETING,即状态值大于1时才会返回,而这里使用的DiscardPolicy拒绝策略什么都没有做,状态值一直为NEW,所以会一直阻塞线程,同样的DiscardOldestPolicy也会存在同样的问题。
那使用默认的AbortPolicy为什么没有问题呢,因为AbortPolicy拒绝策略是抛出RejectedExecutionException异常,此时的future是null并不会调用get()方法
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " +
e.toString());
}
所以当我们在使用Future时最好是使用带超时时间的get方法,这样超时时间到了就会自动返回,也不会使得shutdown失效。当然我们也可以重写DiscardPolicy拒绝策略,在拒绝策略中修改FutureTask的状态值为大于COMPLETING。