Java 线程池(ThreadPoolExecutor)的自动调整策略分析

前言:
最近分析Java源码,对Java的线程池有一些疑惑,特提出来,希望大家能一起讨论确认一下。

  1. Java提供了线程池, ThreadPoolExecutor 实现,可以在 corePoolSize 和 maximumPoolSize 中自动根据传入的任务数自动调整。
  2. 注释中有这样一段话:
If there are more than corePoolSize but less than maximumPoolSize threads running, a new thread will be created only if the queue is full

3.个人觉得这个”is full” 的条件真的是 很傻的设计, 比较合理的设计应该是 “线程数根据提交的任务,自动在 corePoolSize 和 maximumPoolSize 中调整, 和队列是否满没有关系。这样才能够在任务压力低时降低负荷,在任务压力高时及时处理”。

考虑如下场景和需求:
一个服务器希望通过线程池进行服务,为了自动适应网络请求,希望根据请求的任务数自动调整线程数:
- 线程数:最低值为 CPU个数, 最高值为 CPU个数 x 2
- 队列任务数:为了不丢失请求,不设置队列上限

相信这种场景,应该是一个比较常见的需求,但 ThreadPoolExecutor 却无法满足?
当然,一般队列都会设置上限,避免出现内存等问题,不过设置的大的话; 线程无法自动增加,设置的小的话,又会遇到 RejectedExecutionHandler 问题。

附上简单的测试代码:

 static class TestCallable implements Callable<String> {
    private static Random random = new Random(System.currentTimeMillis());
    private int id;
    private String strType;

    TestCallable(String strType, int id) {
      this.strType = strType;
      this.id = id;
    }

    public String call() throws Exception {
      String strThreadName = String.format("%s[%d] in \"%s\"", strType, id, Thread.currentThread().getName());
      int sleepTime = 1000 + random.nextInt(1000);
      log.info("TestCallable, strType={}, will sleep {} ms", strThreadName, sleepTime);
      Thread.sleep(sleepTime);
      return strThreadName;
    }
  }

 @Test
  public void testExecutor() throws Exception {
    int cpuNums = Runtime.getRuntime().availableProcessors();   //获取当前系统的CPU 数目
    final int TASK_COUNT = cpuNums * 2;
    log.info("availableProcessors = {}", cpuNums);
    //newCachedThreadPool, newFixedThreadPool
    ThreadPoolExecutor executor = new ThreadPoolExecutor(0, cpuNums,
        0L, TimeUnit.MILLISECONDS,
        //通过设置 capacity 的值,从日志可以发现Java线程池的问题 -- 队列不满时,即使压了再多的任务,也不会创建新的线程
        new LinkedBlockingQueue<Runnable>(Integer.MAX_VALUE)); //cpuNums * 2

    assertEquals(0, executor.getCorePoolSize());
    assertEquals(cpuNums, executor.getMaximumPoolSize());
    assertEquals(0, getThreadNumberOfThreadPoolExecutor(executor));

    log.info("Before submit {} TestCallable", TASK_COUNT);
    //创建 TASK_COUNT 个任务并执行
    for (int i = 0; i < TASK_COUNT; i++) {
      Future<String> futureReturn = executor.submit(new TestCallable("Cached", i));
      resultList.add(futureReturn);
    }
    log.info("After submit all TestCallable");
    //assertEquals(1, getThreadNumberOfThreadPoolExecutor(executor)); //由于队列未满,只会创建 coreSize 个线程,不会继续增加

    for (Future<String> future : resultList) {
      log.info("Result: " + future.get());
      assertTrue(future.isDone());  //get会阻塞等待任务结束
    }
    log.info("After get all TestCallable result");

运行结果如下(从日志中可以发现, 即使队列中已经有很多任务在等待,但线程池中只有一个线程在运行):

[main] INFO com.fishjam.java.ThreadTest - availableProcessors = 8
[main] INFO com.fishjam.java.ThreadTest - Before submit 16 TestCallable
[main] INFO com.fishjam.java.ThreadTest - After submit all TestCallable
[pool-1-thread-1] INFO com.fishjam.java.ThreadTest - TestCallable, strType=Cached[0] in "pool-1-thread-1", will sleep 1186 ms
[main] INFO com.fishjam.java.ThreadTest - Result: Cached[0] in "pool-1-thread-1"
[pool-1-thread-1] INFO com.fishjam.java.ThreadTest - TestCallable, strType=Cached[1] in "pool-1-thread-1", will sleep 1763 ms
[main] INFO com.fishjam.java.ThreadTest - Result: Cached[1] in "pool-1-thread-1"
[pool-1-thread-1] INFO com.fishjam.java.ThreadTest - TestCallable, strType=Cached[2] in "pool-1-thread-1", will sleep 1326 ms
[main] INFO com.fishjam.java.ThreadTest - Result: Cached[2] in "pool-1-thread-1"
`

猜你喜欢

转载自blog.csdn.net/fishjam/article/details/79079781