JAVA线程池重用线程

时间:2017-11-07 11:58:52

标签: java multithreading threadpool

大家。 我对使用Thread Pools有误解。实际结果与此类的API描述不同。当我在线程池中使用LinkedBlockedQueue并且它不重用线程时,线程池等待在构造函数中设置的KeepAliveTime,然后终止该线程并创建一个新线程。当我将KeepAliveTime设置为较小时,例如1秒或更短时间它会删除线程并重新创建它,但是如果我设置了一分钟,则不会创建新线程,因为MaxPoolSize不允许它并且队列已经满,所以所有任务都被拒绝,但这次keepAliveTime为什么都不做的线程。我很新,不明白为什么它不重用这些线程。在keepTimeAlive到期后,它会杀死这些线程,如果队列已满,则会创建一个新线程。为什么这样工作?据我所知,如果线程在keepAliveTime期间处于空闲状态,它必须重用它。我在使用SynchronousQueue时重用了线程,但没有使用LinkedBlockingQueue

public class Main {

    private volatile int remainingTasksCount;
    private volatile static ThreadPoolExecutor consumer = new ThreadPoolExecutor(1, 2, 1, TimeUnit.MINUTES, new LinkedBlockingQueue<>(3));

    private static Runnable task = () -> {
        System.out.println(String.format("consumer %s, id %s, size %s, active count %s, queue %s",
                Thread.currentThread().getName(), Thread.currentThread().getId(),
                consumer.getPoolSize(), consumer.getActiveCount(), 3-consumer.getQueue().remainingCapacity()));
        String s = new String();
        synchronized (s) {
            try {
                s.wait(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    };


    public static void main(String[] args) throws IOException {
        try {
            new Thread(() -> {
                while (true) {
                    try {
                        for (int i = 0; i < 5; i++) {
                            consumer.submit(task);
                        }
                        System.out.println("PUSH TASKS");
                        synchronized (Thread.currentThread()) {
                            Thread.currentThread().wait(10000);
                        }
                    } catch (Throwable th) {
                        System.out.println(th);
                    }
                }
            }).start();
        } catch (Throwable th) {
            System.out.println(th);
        }
    }  

输出

PUSH TASKS
consumer pool-1-thread-1, id 15, size 2, active count 2, queue 3
consumer pool-1-thread-2, id 16, size 2, active count 2, queue 3
consumer pool-1-thread-2, id 16, size 2, active count 2, queue 1
consumer pool-1-thread-1, id 15, size 2, active count 1, queue 2
consumer pool-1-thread-1, id 15, size 2, active count 1, queue 0
Disconnected from the target VM, address: '127.0.0.1:64434', transport: 'socket'

Process finished with exit code 1

但是下次制作人提交任务时,我会得到RejectedExecutionException

如果我将keepAliveTime更改为1 Second。一切都运作良好,但创造 新主题。

PUSH TASKS
consumer pool-1-thread-2, id 16, size 2, active count 2, queue 3
consumer pool-1-thread-1, id 15, size 2, active count 2, queue 3
consumer pool-1-thread-2, id 16, size 2, active count 2, queue 2
consumer pool-1-thread-1, id 15, size 2, active count 2, queue 1
consumer pool-1-thread-2, id 16, size 2, active count 1, queue 0
PUSH TASKS
consumer pool-1-thread-3, id 17, size 2, active count 2, queue 3
consumer pool-1-thread-2, id 16, size 2, active count 2, queue 2
consumer pool-1-thread-3, id 17, size 2, active count 2, queue 1
consumer pool-1-thread-2, id 16, size 2, active count 2, queue 1
consumer pool-1-thread-3, id 17, size 2, active count 1, queue 0
consumer pool-1-thread-3, id 17, size 1, active count 1, queue 2
PUSH TASKS
consumer pool-1-thread-4, id 18, size 2, active count 2, queue 3
consumer pool-1-thread-3, id 17, size 2, active count 2, queue 1
consumer pool-1-thread-4, id 18, size 2, active count 2, queue 1
consumer pool-1-thread-3, id 17, size 2, active count 1, queue 0
PUSH TASKS
consumer pool-1-thread-3, id 17, size 2, active count 2, queue 2
consumer pool-1-thread-5, id 19, size 2, active count 2, queue 3
consumer pool-1-thread-3, id 17, size 2, active count 2, queue 1
consumer pool-1-thread-5, id 19, size 2, active count 2, queue 1
consumer pool-1-thread-3, id 17, size 2, active count 1, queue 0

如果有人能解释我的错误,或者我错过的一些基本原则,我会很高兴

2 个答案:

答案 0 :(得分:3)

这是竞争条件。如果您按照submit()足够长的时间(在源代码中),您将到达ThreadPoolExecutor.execucte()

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    /* long comment block removed */
    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循环首次运行时,execute将创建新的工作人员,并为他们提供您的任务,而不会尝试将他们推入队列(addWorker + {{ 1}}),所以2个任务立即开始,3个进入队列,可以容纳所有3个任务。

第二次,return - s将以submit结束,这可能会使队列饱和(取决于工人尝试消耗新项目的速度),以及何时事实上,最后的努力workQueue.offer将会运行并失败,从而导致addWorker,因为不允许创建新的工作人员。

实际上如果你开始做事情&#39;在你的提交循环中,它最终将开始工作。例如,我尝试reject,这足够慢,可以消耗一些任务,循环成功。当我尝试已经过快的println(i)时,它会在第4次提交时死亡,因此很快就没有消耗任务。因此,这是一个微妙的问题,通常是竞争条件。

答案 1 :(得分:0)

我认为由于您的代码示例,您对线程池的工作原理有一些误解。我尝试运行它,然后从5个任务和无限数量的RejectedExecutionException获取输出。发生这种情况是因为如果未调用异常Thread.currentThread().wait(10000);,则会向池中添加另外5个任务,并且此逻辑会一次又一次地重复产生新的异常。尝试包围consumer.submit(任务);使用try-catch块,您将看到只有两个线程按预期处理所有任务,因为keepTimeAlive比等待时间长。在第二个示例中keepTimeAlive比等待时间短,因此在每次等待之后创建新的非核心线程,并且在每次循环调用之后会看到不同的ID。这是正确的,因为之前的非核心线程因空闲时间超过keepTimeAlive而被停止。