hippo4j icon indicating copy to clipboard operation
hippo4j copied to clipboard

[Question] Provide boolean parameter to support prestartCoreThread or prestartAllCoreThreads

Open jjiey opened this issue 1 year ago • 4 comments

需求建议

请在提交问题之前回答这些问题,谢谢。

您的功能请求是否与问题有关?

描述你想要的功能

  1. config 模式

ExecutorProperties 提供 boolean 参数支持 prestartCoreThread or prestartAllCoreThreads

  1. server 模式可提供相关按钮和接口来支持 prestartCoreThread or prestartAllCoreThreads

  2. 支持该功能的必要性

目前如果用户有此需求,会通过手动调用 prestartCoreThread or prestartAllCoreThreads。假设以下场景:config 模式下

@Bean
@DynamicThreadPool
public ThreadPoolExecutor threadPoolExecutor() {
    ThreadPoolExecutor tpe = ThreadPoolBuilder.builder()
            .corePoolSize(10)
            .maxPoolNum(10)
            .threadFactory("xxx")
            .threadPoolId("xxx")
            .waitForTasksToCompleteOnShutdown(true)
            .dynamicPool()
            .build();
    tpe.prestartAllCoreThreads();
    return tpe;
}

配置文件中指定:

thread-pool-id: 'xxx'
core-pool-size: 10
maximum-pool-size: 10
blocking-queue: 'LinkedBlockingQueue'
queue-capacity: 100
  • build() 时 tpe 中阻塞队列会设置默认 queue(称为 queue1),调用 prestartAllCoreThreads 后,10 条核心线程阻塞在 queue1 中等待获取任务

  • 后续通过配置将 tpe 中阻塞队列设置为新的 queue(称为 queue2)

// DynamicThreadPoolPostProcessor#threadPoolParamReplace
private void threadPoolParamReplace(ThreadPoolExecutor executor, ExecutorProperties executorProperties) {
    BlockingQueue workQueue = BlockingQueueTypeEnum.createBlockingQueue(executorProperties.getBlockingQueue(), executorProperties.getQueueCapacity());
    ReflectUtil.setFieldValue(executor, "workQueue", workQueue);
    ...
}
  • 当服务启动完成,tpe 开始工作时,该线程池中的 10 条线程将永远阻塞在 queue1 等待获取任务,但其实所有任务进 queue2 却无线程去执行 queue2 中的任务,导致后续出现问题

jjiey avatar Mar 12 '23 02:03 jjiey

在替换动态线程池前,会有任务执行么?

magestacks avatar Mar 12 '23 03:03 magestacks

@magestacks 更新了一下之前的描述,详细描述了下场景

jjiey avatar Mar 13 '23 02:03 jjiey

@magestacks 复现步骤:

  1. 新建 springboot 项目,引入 hippo4j-config-spring-boot-starter 1.4.3-upgrade
  2. application.yml
spring:
  dynamic:
    thread-pool:
      banner: false
      notify-platforms:
      executors:
        - thread-pool-id: 'xxx'
          core-pool-size: 5
          maximum-pool-size: 5
          blocking-queue: 'LinkedBlockingQueue'
          queue-capacity: 10
          keep-alive-time: 60
          allow-core-thread-time-out: false
  1. DemoApplication.java
@SpringBootApplication
@EnableDynamicThreadPool
public class DemoApplication {

    @Bean
    @DynamicThreadPool
    public ThreadPoolExecutor testThreadPoolExecutor() {
        ThreadPoolExecutor tpe = ThreadPoolBuilder.builder()
                .corePoolSize(5)
                .maxPoolNum(5)
                .threadFactory("xxx")
                .threadPoolId("xxx")
                .dynamicPool()
                .build();
        tpe.prestartAllCoreThreads();
        return tpe;
    }

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}
  1. TestController.java
@Slf4j
@RestController
@RequestMapping("/")
public class TestController {

    @Autowired
    private ThreadPoolExecutor testThreadPoolExecutor;

    @GetMapping("test")
    public void test() {
        for (int i = 0; i < 20; i++) {
            testThreadPoolExecutor.execute(() -> System.out.println("ignore"));
            BlockingQueue<Runnable> queue = testThreadPoolExecutor.getQueue();
            log.info("queue.size(): " + queue.size());
            log.info("queue.remainingCapacity(): " + queue.remainingCapacity());
        }
    }
}
  1. 启动服务请求 http://localhost:8080/test 日志: image
  2. 原因: image image 圈错了,看 workqueue image image

该线程池中的 5 条线程将永远阻塞在 queue 6354 等待获取任务,但其实所有任务进 queue 6385 却无线程去执行 queue 6385 中的任务,导致后续队列满出现拒绝策略

jjiey avatar Mar 20 '23 10:03 jjiey

是的。建议线程池的queue不要直接替换,而应该使用代理的方式,将原始的queue取出完成代理后使用反射(<jdk8)或unsafe(>=jdk8)替换原始的queue引用就可以了。 只要线程池内在被代理前有活动线程或者queue里已经有任务时就会出现上边的情况。但只要queue替换前有线程启动就会有问题,问题的大小就看启动的线程多少了。

fuxiuzhan avatar Sep 07 '23 10:09 fuxiuzhan