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

时间:2017-08-09 21:59:32

标签: java multithreading executorservice

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

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

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

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

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

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

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

3 个答案:

答案 0 :(得分:1)

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

答案 1 :(得分:1)

可以使用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. 所以现在假设我们将核心大小设置为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以上的线程被杀死。仅当corePoolSize用尽时,线程池才会增加workQueue以上。

    因此maxPoolSize是一个故障安全选项,可以避免队列耗尽。不是相反。最大池大小不是为了杀死空闲线程。

    如果我们将队列大小设置得太小,我们就会冒任务拒绝的风险。如果我们设置得太高,poolSize将永远不会越过corePoolSize

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

答案 2 :(得分: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);
     }
});