Java ExecutorService - 处于等待状态的线程

Java ExecutorService - threads in waiting state

用例:每次我需要处理作业时创建一个新线程。

当前实施:我正在使用具有固定大小线程池的执行器服务,比如说 50。对于每个作业,我都向执行器服务提交一个新线程。

问题:作业完成后,线程不会死亡并进入等待状态。 (在 sun.misc.unsafe.park 等候)

分析:根据这个 link (WAITING at sun.misc.Unsafe.park(Native Method)) 和网络上的其他来源,这是一个有效的场景,线程进入等待状态,等待一些任务被赋予给他们。

问题:从 Java 任务控制,我可以推断出线程没有使用任何资源并且没有处于死锁状态。所以这很好。但是请考虑提交大量作业并且实例化池中所有 50 个线程的时间范围。之后所有 50 个线程都将处于活动状态,即使作业提交率可能有所下降。我也不能关闭执行程序服务,因为它需要永远活着等待作业被提交。 如果我创建普通线程,我会看到线程在完成工作后死亡。但是在那种情况下,创建的最大线程数中没有选项卡。所以在高峰期,我们可能会 运行 陷入创建的线程数量超过 JVM 处理能力的情况。

如何以最好的方式处理这种情况。我们应该忽略处于等待状态的线程还是应该进行任何其他实现。

我尝试实现的行为更像是自动缩放。在高峰时段跨越更多服务器(在本例中为线程)。并在负载不是那么高时终止额外的服务器并保持最少的服务器数量。

使用 ThreadPoolExecutor 并通过其任一构造函数设置其 keepAliveTime 属性。

可以使用 ThreadPoolExecutor 来完成。然而,它不会做你期望它做的事。以下构造函数可用于创建 ThreadPoolExecutor.

ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue)

让我按照记录来分解它的行为。提交任务时

  1. 如果poolSize小于corePoolSize,即使有空闲线程,也会创建一个新线程。
  2. 如果 poolSize 等于 corePoolSize,则任务被添加到队列中。在队列耗尽之前,它不会创建新线程。
  3. 如果 workQueue 耗尽,则创建新线程,直到 poolSize 变为 maximumPoolSize
  4. 如果poolSize等于maximumPoolSize则抛出RejectedExecutionException

现在假设我们将核心大小设置为 5,将最大大小设置为 10 并提交 100 个任务。如果我们使用 Executors class. As pools created by it use LinkedBlockingQueue, with default constructor, which sets the queue capacity to measly Integer.MAX_VALUE (2147483647).

创建池对象,则不会发生任何事情

以下是来自Executors

的代码
public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

LinkedBlockingQueue

中的默认构造函数
public LinkedBlockingQueue() {
    this(Integer.MAX_VALUE);
}
public LinkedBlockingQueue(int capacity) {
...

直接创建 ThreadPoolExecutor 的选项仍然存在,但这并没有太大帮助。让我们检查一下。假设我们使用以下代码创建 ThreadPoolExecutor 对象。

ArrayBlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(MAX_QUEUE_SIZE);
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE, MAX_POOL_SIZE, IDLE_LIFE_TIME, TimeUnit.SECONDS, workQueue);

其中MAX_QUEUE_SIZE为10,可提交的最大任务数可通过以下公式计算。

MAX_TASKS = MAX_POOL_SIZE + WORK_QUEUE_CAPACITY

因此,如果最大池大小为 10 且工作队列大小也为 10,则如果没有线程空闲,则第 21 个任务将被拒绝。

重要的是要记住它不会给我们期望的行为。因为线程只有在线程数超过 corePoolSize 时才会被杀死。仅当 workQueue 已耗尽时,线程池才会增加超过 corePoolSize

所以 maxPoolSize 是避免队列耗尽的故障保护选项。不是相反。最大池大小并不是为了杀死空闲线程。

如果我们将队列大小设置得太小,我们就有可能拒绝任务。如果我们将它设置得太高,poolSize 将永远不会超过 corePoolSize

也许您可以探索 ThreadPoolExecutor.setRejectedExecutionHandler。并将被拒绝的任务保存在一个单独的队列中,一旦 workQueue.capacity 定期小于最大容量,该队列就会将任务发送到 workQueue。但这似乎没有同等收益的大量工作。

And after that all the 50 threads are going to be alive even though the job submission rate might have decreased. I cannot shut down the executor service also, since it needs to be alive forever waiting for jobs to be submitted.

...

How can this scenario be handled in the best possible way. Should we ignore the threads being in the waiting state or should I go for any other implementation.

我认为答案是,你应该忽略它们。如今,线程非常高效,而且 50 个休眠线程肯定不会以任何方式影响应用程序的运行时间。如果您谈论的是大量线程或一系列不同的线程池,情况就会有所不同。

就是说,如前所述,如果您希望您的线程超时,那么您将需要指定与 "max"(池也可以增长的最大数量)以及线程在退出之前应该开始休眠多长时间,以将线程计数保持在 "core" 数量。这样做的问题是你需要有一个固定大小的作业队列,否则永远不会创建第二个线程。这就是(不幸的)ThreadPoolExecutor 的工作原理。

如果您有不同的核心和最大线程数,并且您正在向线程池提交大量作业,那么您需要阻止生产者,否则作业将在队列满时被拒绝向上。

类似于:

ThreadPoolExecutor threadPool = new ThreadPoolExecutor(CORE_POOL_SIZE, MAX_POOL_SIZE,
    60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(MAX_QUEUE_SIZE));
// need to say what to do if the queue is full
threadPool.setRejectedExecutionHandler(new RejectedExecutionHandler() {
     public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
          // this will block the caller if the queue is full
          executor.getQueue().put(r);
     }
});

最好在完成后关闭执行程序。 它将释放所有使用执行程序服务创建的线程。

finally {
        if(!executors.isShutdown())
            executors.shutdown();
    }