繁体   English   中英

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

[英]Java ExecutorService - threads in waiting state

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

当前实现:我使用 Executor Service 和固定大小的线程池,比如 50。对于每个作业,我向执行程序服务提交一个新线程。

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

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

问题:从 Java 任务控制中,我能够推断出线程没有使用任何资源并且没有处于死锁状态。 所以这很好。 但请考虑提交大量作业并且池中的所有 50 个线程都已实例化的时间范围。 之后,即使作业提交率可能下降,所有 50 个线程都将处于活动状态。 我也不能关闭 executor 服务,因为它需要永远活着等待提交作业。 如果我创建普通线程,我会看到线程在完成工作后死亡。 但在这种情况下,正在创建的最大线程数中没有选项卡。 因此,在高峰时间,我们可能会遇到创建的线程数超过 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类创建池对象,则不会发生任何事情。 由于它创建的池使用LinkedBlockingQueue ,带有默认构造函数,它将队列容量设置为微不足道的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将永远不会poolSize corePoolSize

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

之后,即使作业提交率可能下降,所有 50 个线程都将处于活动状态。 我也不能关闭 executor 服务,因为它需要永远活着等待提交作业。

...

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

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

也就是说,如上所述,如果您希望线程超时,则需要指定与“最大”(池也可以增长的最大数量)不同的“核心”线程数(应该始终运行多少)以及线程在退出之前应该开始休眠多长时间以将线程计数保持在“核心”数。 这样做的问题是,您需要有一个固定大小的作业队列,否则将永远不会创建第二个线程。 这就是(不幸的是) 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);
     }
});

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM