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

Posted

技术标签:

【中文标题】Java ExecutorService - 处于等待状态的线程【英文标题】:Java ExecutorService - threads in waiting state 【发布时间】: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”毫秒内没有收到任何作业,则终止线程。更像是自动缩放功能,您可以在高峰时段跨越更多服务器,并在不需要时关闭它们。 【参考方案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);
     
);

【讨论】:

您好,请问这 (***.com/q/59231602) 是否相关?【参考方案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)

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

    如果poolSize 小于corePoolSize,即使有空闲线程,也会创建一个新线程。 如果poolSize 等于corePoolSize,则将任务添加到队列中。在队列耗尽之前,它不会创建新线程。 如果workQueue 用尽,则创建新线程,直到poolSize 变为maximumPoolSize。 如果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 将永远不会超过corePoolSize

也许你可以探索ThreadPoolExecutor.setRejectedExecutionHandler。并将被拒绝的任务保存在一个单独的队列中,一旦workQueue.capacity 周期性地小于最大容量,它将向workQueue 发送任务。但这似乎需要做很多工作而没有同等的收获。

【讨论】:

【参考方案4】:

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

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

【讨论】:

以上是关于Java ExecutorService - 处于等待状态的线程的主要内容,如果未能解决你的问题,请参考以下文章

java并发中ExecutorService的使用

Java线程池 ExecutorService

Java基础ExecutorService的使用

Java线程池 ExecutorService了解一下

Java 中这段代码中的 ExecutorService.submit 和 ExecutorService.execute 有啥区别?

Java并发之ExecutorService