1. Introduction
In development, batch processing business is sometimes encountered. If single thread processing, the speed will be very slow, which may lead to upstream timeout. This is the need to use multithreaded development.
Thread pools should be used when creating threads. On the one hand, it avoids the cost of creating and destroying threads when processing tasks, on the other hand, it avoids the excessive scheduling problem caused by the expansion of the number of threads, and ensures the full utilization of the kernel.
You can use the thread pool provided by J.U.C: ThreadPoolExecutor class. In the Spring framework, you can also use the ThreadPoolTaskExecutor class. ThreadPoolTaskExecutor is actually an encapsulation of ThreadPoolExecutor.
2. Using the ThreadPoolExecutor class
Assuming the existing business, Input class and Output class:
@Data @AllArgsConstructor public class Input { int i; } @Data @AllArgsConstructor public class Output { boolean success; String s; }
Here @ Data and @ allargsconstructor use the Lombok tool
Treatment method:
public Output singleProcess(Input input) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); return new Output(false, null); } return new Output(true, String.valueOf(2 * input.getI() + 1)) }
Now the business needs batch processing. Enter List < input > and output List < output >. Then you can create a thread pool with 4 core threads, and each thread adds the execution result to the thread safe List. Here, you should use synchronized List instead of CopyOnWriteArrayList, because there are multiple write operations and only one read operation. And use CountDownLatch to wait for all threads to finish executing:
public List<Output> multiProcess(List<Input> inputList) { ExecutorService executorService = Executors.newFixedThreadPool(4); CountDownLatch countDownLatch = new CountDownLatch(inputList.size()); List<Output> outputList = Collections.synchronizedList(new ArrayList<>(inputList.size())); for (Input input : inputList) { executorService.submit(() -> { try { // Single processing Output output = singleProcess(input); outputList.add(ouput); } catch (Exception e) { // Handling exceptions } finally { countDownLatch.countDown(); } }) } // Wait for all threads to complete execution try { countDownLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } return outputList; }
But there are still big problems:
- Alibaba development manual does not recommend that we use Executors to create thread pools because the Executors.newFixedThreadPool method does not limit the capacity of thread queues. If the number of input s is too large, it may lead to OOM.
- multiProcess is not suitable for multiple calls and is not suitable for most business scenarios.
3. Using the ThreadPoolTaskExecutor class in the Spring framework
In order to cope with most business scenarios, we can use the ThreadPoolTaskExecutor to create a thread pool and inject it into the ioc container, which can be used globally, in conjunction with the Spring Boot framework.
First, configure thread pool parameters
@Data @Component @ConfigurationProperties(prefix = "thread-pool") public class ThreadPoolProperties { private int corePoolSize; private int maxPoolSize; private int queueCapacity; private int keepAliveSeconds; }
In the configuration file application.yml
thread-pool:
core-pool-size: 4
max-pool-size: 16
queue-capacity: 80
keep-alive-seconds: 120
Here, you can refer to the meaning of thread pool parameters Implementation principle of Java thread pool and its practice in meituan business
Second, add the ThreadPoolTaskExecutor to the ioc container
@EnableAsync @Configuration public class ThreadPoolConfig { private final ThreadPoolProperties threadPoolProperties; @Autowired public ThreadPoolConfig(ThreadPoolProperties threadPoolProperties) { this.threadPoolProperties = threadPoolProperties; } @Bean(name = "threadPoolTaskExecutor") public ThreadPoolTaskExecutor threadPoolTaskExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(threadPoolProperties.getCorePoolSize()); executor.setMaxPoolSize(threadPoolProperties.getMaxPoolSize()); executor.setQueueCapacity(threadPoolProperties.getQueueCapacity()); executor.setKeepAliveSeconds(threadPoolProperties.getKeepAliveSeconds()); executor.setThreadNamePrefix("thread-pool-"); return executor; } }
Here @ EnableAsync is used in conjunction with @ Async to execute asynchronous tasks. Examples will be given later
Finally, in the business class, get the bean by customizing the SpringUtils class or use @ Async to use the thread pool.
/** * Business implementation class */ @Service @Slf4j public class Input2OutputServiceImpl implements Input2OutputService { /** * Single processing * @param input Input object * @return Output object */ @Override public Output singleProcess(Input input) { log.info("Processing..."); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); return new Output(false, null); } return new Output(true, String.valueOf(2 * input.getI() + 1)); } /** * Batch processing * @param inputList Input object list * @return Output object list */ @Override public List<Output> multiProcess(List<Input> inputList) { ThreadPoolTaskExecutor executor = SpringUtils.getBean("threadPoolTaskExecutor", ThreadPoolTaskExecutor.class); CountDownLatch latch = new CountDownLatch(inputList.size()); List<Output> outputList = Collections.synchronizedList(new ArrayList<>(inputList.size())); for (Input input : inputList) { executor.execute(() -> { try { Output output = singleProcess(input); outputList.add(output); } catch (Exception e) { e.printStackTrace(); } finally { latch.countDown(); } }); } try { latch.await(); } catch (InterruptedException e) { e.printStackTrace(); } return outputList; } /** * Asynchronous processing * @param input Input object * @return Export Future objects */ @Async("threadPoolTaskExecutor") @Override public Future<Output> asyncProcess(Input input) { return new AsyncResult<>(singleProcess(input)); } }
The complete code of the above code includes the test code in the author's GitHub project thread-pool-demo , the ThreadPoolTaskExecutor used in the project can be referred to.