【问题标题】:Java ExecutorService - threads in waiting stateJava ExecutorService - 处于等待状态的线程
【发布时间】:2018-01-17 23:16:27
【问题描述】:

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

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

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

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

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

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

我试图实现的行为更像是自动缩放。在高峰时间跨越更多服务器(在这种情况下为线程)。并在负载不那么高时终止额外的服务器并保持最少的服务器数量。

【问题讨论】:

  • 这不是对象池的预期行为吗?
  • 这可能是线程池的默认行为。但在我的情况下,所有线程在高峰时间都处于活动状态,之后它们总是处于等待状态,之后一次只会使用很少的线程。所以想知道 Executor Service 是否还有其他设置可以使用。或任何其他可以解决这种情况的实现。
  • 如果您希望每个作业都有一个新线程,为什么要使用ExecutorService?如果你不这样做,你为什么要说第一句话?
  • @EJP.. 如果我要为每个作业创建一个新线程,使用 new Thread(new My Runnable()).start.. 那么我如何密切关注峰值期间创建的最大线程数次。不会出现在达到最大资源时抛出异常的情况。这就是我尝试使用并发包提供的线程创建框架的原因。我的要求是通过创建线程来处理所有作业,但如果线程在“n”毫秒内没有收到任何作业,则终止线程。更像是自动缩放功能,您可以在高峰时段跨越更多服务器,并在不需要时关闭它们。

标签: java multithreading executorservice


【解决方案1】:

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

...

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

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

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

【讨论】:

【解决方案2】:

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

【讨论】:

  • 但是我正在使用的 Executors.newFixedThreadPool(int n) 反过来使用 KeepAliveTime 为 0 的 ThreadPoolExecutor。所以我们不必在实现中显式使用 ThreadPoolExecutor。同样在这种情况下,当 keepAliveTime 为零时,我希望线程立即死亡,但事实并非如此。
【解决方案3】:

可以使用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_VALUE2147483647)。

以下是来自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 发送任务。但这似乎需要做很多工作而没有同等的收获。

【讨论】:

    【解决方案4】:

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-01-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多