线程池使用FutureTask时注意事项

       线程池使用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。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值