为什么空闲线程数量大于提交任务数量,任务却被拒绝了

209 阅读4分钟

背景: 有个定时任务每次批处理发送消息,为了提高并发能力,使用线程池 线程池的设置类似:

每次提交的任务数量最多也就20个,而且很快就能处理完,代码实例如下: 运行这段代码就会出现跑异常的问题

public static void main(String[] args) throws Exception {
    ExecutorService executorService =
            new ThreadPoolExecutor(1, 20, 10L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(2));
    //先提交20个任务
    for (int i = 0; i < 20; i++) {
        int finalI = i;
        executorService.submit(() -> {
            System.out.println(Thread.currentThread().getName() + "i=" + finalI);
            try {
                Thread.sleep(10);
                System.out.println("执行完任务" + Thread.currentThread().getName() + "i=" + finalI);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
    }
    //让这20个任务跑完
    Thread.sleep(100);
    //继续提交20个任务,这时可能会触发拒绝策略
    for (int i = 0; i < 20; i++) {
        int finalI = i;
        executorService.submit(() -> {
            System.out.println(Thread.currentThread().getName() + "i=" + finalI);
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
    }
}

很明显这个错误表示线程池拒绝了提交的任务,为什么最大线程都空闲,任务数量也少于最大线程数的情况下任务会被拒绝呢?组里同事看了也都不明白为什么?没办法只能看源码

线程池是如何调度任务的

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    int c = ctl.get();
    // 如果worker小于核心线程数,创建work
    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)
            //这里command传入null是因为,已经添加到队列中了,如果配置的线程池是核心是0,线程池的线程全部被回收的情况下,这个任务就一直会卡住了
            addWorker(null, false);
    }
    // 队列满就尝试创建work,并且work数量小于最大线程数量
    else if (!addWorker(command, false))
    // 如果work数量已经等于最大线程数量,直接拒绝跑出异常,大部分是这里抛出异常
        reject(command);
}

创建的worker即被封装的线程在干什么,什么时候会被销毁

private final class Worker
    extends AbstractQueuedSynchronizer
    implements Runnable
{
   
    private static final long serialVersionUID = 6138294804551838833L;

    final Thread thread;
    Runnable firstTask;
    volatile long completedTasks;

    Worker(Runnable firstTask) {
        setState(-1); // inhibit interrupts until runWorker
        this.firstTask = firstTask;
        // 这里表示创建的线程运行的任务就是work的run方法,即run方法就是worker做的事情-
        this.thread = getThreadFactory().newThread(this);
    }
    
    public void run() {
        runWorker(this);
    }

}

worker的核心逻辑,就是执行传进来的第一个task,执行完后就不断拉取队列中的任务去执行

final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    w.firstTask = null;
    w.unlock(); // allow interrupts
    boolean completedAbruptly = true;
    try {
    // 这里可以看到就是一直在获取任务,好像没什么问题,getTask()返回null的话就会跳出循环销毁任务
        while (task != null || (task = getTask()) != null) {
            w.lock();
            if ((runStateAtLeast(ctl.get(), STOP) ||
                 (Thread.interrupted() &&
                  runStateAtLeast(ctl.get(), STOP))) &&
                !wt.isInterrupted())
                wt.interrupt();
            try {
                beforeExecute(wt, task);
                Throwable thrown = null;
                try {
                    //运行任务
                    task.run();
                } catch (RuntimeException x) {
                    thrown = x; throw x;
                } catch (Error x) {
                    thrown = x; throw x;
                } catch (Throwable x) {
                    thrown = x; throw new Error(x);
                } finally {
                    afterExecute(task, thrown);
                }
            } finally {
                //执行完一个任务就设置为null,尝试获取下一个任务
                task = null;
                w.completedTasks++;
                w.unlock();
            }
        }
        completedAbruptly = false;
    } finally {
    // 销毁worker
        processWorkerExit(w, completedAbruptly);
    }
}
private Runnable getTask() {
    boolean timedOut = false; // Did the last poll() time out?

    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);

        // Check if queue empty only if necessary.
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            decrementWorkerCount();
            return null;
        }

        int wc = workerCountOf(c);

        // 这里比较关键如果当前线程大于核心线程timed返回true
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

        if ((wc > maximumPoolSize || (timed && timedOut))
            && (wc > 1 || workQueue.isEmpty())) {
            if (compareAndDecrementWorkerCount(c))
                return null;
            continue;
        }

        try {
            // 这里能看到timed为true会poll拉取任务,这里如果超过keepAliveTime就会返回null,这里返回null上边的获取任务while循环会结束,线程会销毁,这里能解释为什么超过核心线程数会被销毁,如果为fasle会执行take,队列为空这里是会一直阻塞直到获得task,这里也能解释为什么核心线程不会被销毁。
            Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                workQueue.take();
            if (r != null)
                return r;
            timedOut = true;
        } catch (InterruptedException retry) {
            timedOut = false;
        }
    }
}

核心代码大致分析完了,再回头看问题,被抛出异常肯定是队列满了,并且当前线程数达到了最大线程数,线程池的线程只做两件事情:1、执行当前任务2、拉取队列中的任务 提交任务的时候哪怕之前的任务已经全部执行完了,只要最大线程还在poll的aliveTime时间内就不会被销毁,这时候我们提交任务就是往队列中add,work在poll/take,如果我们add的速度大于worker拉取的速度时就是堆积甚至队列满,如果你的队列很小被打满的概率就很大了。 看到这里你应该知道怎么做了,把队列跳大一些就能解决问题