[Java Concurrent Programming] Simple Application of ThreadPoolTaskExecutor Thread Pool

The thread pool class ThreadPoolExecutor is provided in Java JDK, but SpringBoot is often used for development in actual development. Spring also comes with a thread pool by default to facilitate our development. It is ThreadPoolTaskExecutor; I have read many articles about ThreadPoolTaskExecutor. Introduce it from the principle and configuration, but when actually writing the code, you also need to consider how to design and use it. This may not be a problem for veterans, but it may be confusing for novices who are just starting to use.
Below I will introduce from the way I used in the actual development process.

1. Design ideas

ThreadPoolTaskExecutor
The interface class of Spring's asynchronous thread pool is TaskExecutor, which is essentially java.util.concurrent.Executor. If there is no configuration, simpleAsyncTaskExecutor is used by default. But Spring recommends that our developers use the ThreadPoolTaskExecutor class to create a thread pool, which is essentially a wrapper for java.util.concurrent.ThreadPoolExecutor.
In the actual programming process, we may encounter many calculation-intensive or IO-intensive operations. In many cases, we need to operate on the data in a Collection. At this time, we thought that it would be great if we could use the thread pool to operate. Then there is this article.
Result Acquisition
Use the Future class to obtain the results of multi-threaded operations. Java supports the requirement of obtaining task execution results through 3 submit() methods and 1 FutureTask tool class provided by ThreadPoolExecutor. As a wrapper for ThreadPoolExecutor, ThreadPoolTaskExecutor will naturally provide these three submit methods.

// 提交Runnable任务
Future<?> 
  submit(Runnable task);
// 提交Callable任务
<T> Future<T> 
  submit(Callable<T> task);
// 提交Runnable任务及结果引用  
<T> Future<T> 
  submit(Runnable task, T result);

Because the Run method of the Runable interface has no return value, the value obtained by its future method can only be used to assert whether the task has ended. Callable has a return value, and we can use this interface to get the result of the operation. The third interface is a bit more complex and doesn't fit our idea of ​​a simple application. As for FutureTask, I will talk about it later.

2. Concrete implementation

  1. Configure thread pool
package com.lordum.threadpoolpractice.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

@Configuration
@EnableAsync
public class TaskPoolConfig {
    
    

    @Bean("taskExecutor")
    public ThreadPoolTaskExecutor taskExecutor(){
    
    
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        taskExecutor.setCorePoolSize(10);
        taskExecutor.setMaxPoolSize(20);
        taskExecutor.setQueueCapacity(200);
        taskExecutor.setKeepAliveSeconds(60);
        taskExecutor.setThreadNamePrefix("taskExecutor--");
        taskExecutor.setWaitForTasksToCompleteOnShutdown(true);
        taskExecutor.setAwaitTerminationSeconds(60);
        return taskExecutor;
    }
}
  1. Create a task that implements the Callable interface
package com.lordum.threadpoolpractice.tasks;

import java.util.concurrent.Callable;

public abstract class AbstractTask implements Callable<Result> {
    
    

}
package com.lordum.threadpoolpractice.tasks;

import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import java.time.LocalDateTime;
import java.util.Date;
import java.util.concurrent.TimeUnit;

@AllArgsConstructor
@NoArgsConstructor
@Slf4j
public class DemoTask extends AbstractTask{
    
    
    private String name;

    @Override
    public Result call() throws Exception {
    
    
        Result result = new Result();
        log.info(Thread.currentThread().getName() + "接收到任务:" + name + ". 当前时间:" + LocalDateTime.now().toString());
        result.setData("Call " + name);
        TimeUnit.SECONDS.sleep(2);
        return result;
    }
}

result receiving class

package com.lordum.threadpoolpractice.tasks;

import lombok.Data;

@Data
public class Result {
    
    
    private Integer code = 1;//1:成功  0:失败
    private String desc;//描述
    private Object data;
}
  1. call service
package com.lordum.threadpoolpractice.service;

import com.lordum.threadpoolpractice.tasks.DemoTask;
import com.lordum.threadpoolpractice.tasks.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

@Slf4j
@Service
public class DemoService {
    
    

    @Autowired
    @Qualifier("taskExecutor")
    private ThreadPoolTaskExecutor taskExecutor;


    public List<String> useTaskPoolDemo() throws ExecutionException, InterruptedException {
    
    
        List<String> strings = Arrays.asList("Spider man", "Haoke", "Mary", "Lilei", "Han Meimei");
        long start = System.currentTimeMillis();
        List<String> resultList = new ArrayList<>();
        List<Future<Result>> futureList = new ArrayList<>();
        for (String string : strings) {
    
    
            DemoTask demoTask = new DemoTask(string);
            Future<Result> result = this.taskExecutor.submit(demoTask);
            futureList.add(result);
            log.info("##" + string +"## 添加到结果: " + string + "结果时间: " + LocalDateTime.now());
        }
        // future.get() 会阻塞调用线程(主线程),如果在上面的循环中获取,整个服务就会变成并行,失去使用线程池的意义
        for (Future<Result> resultFuture : futureList) {
    
    
            Result data = resultFuture.get();
            log.info("## 获取到future结果: " + data.getData().toString() + "结果时间: " + LocalDateTime.now());
            resultList.add(data.getData().toString());
        }
        long end = System.currentTimeMillis();
        log.info("程序执行时间:" + String.valueOf(end - start));
        return resultList;
    }
}

3. Summary

advantage

  1. The thread pool can be shared, and there is no need to create a thread pool for each service
  2. It is easy to expand. If there are other services that need to use the thread pool, they can inherit AbstractTask for expansion.

Note that
future.get() will block the calling thread (main thread). If it is obtained in the loop of calling the thread pool, the entire service will become parallel and lose the meaning of using the thread pool

The results show
insert image description here
that each of our parallel services takes 2 seconds. If the thread pool is not used, it needs to be executed for more than 10 seconds. After using the thread pool, the value is 2s11ms. It can be seen that parallel processing using the thread pool really improves the execution speed.

Guess you like

Origin blog.csdn.net/zhoulizhu/article/details/125271979