Introducing the actual application scenarios of Spring Boot multi-threading and how to optimize multi-threading performance

Spring Boot is a popular Java framework that allows you to quickly create and run Spring-based applications. Developing in Spring Boot, there are some practical application scenarios, as well as some techniques that can optimize multi-threaded performance. In this blog, I will introduce some common scenarios and techniques that I hope will be helpful to you.

Practical application scenarios

When developing in Spring Boot, sometimes we need to handle some time-consuming tasks, such as calling external APIs, performing complex calculations, or processing large amounts of data. These tasks may block the main thread, causing the application to become less responsive or even cause timeouts or memory overflows. In order to solve these problems, we can use multi-threading to perform these tasks concurrently and improve the efficiency and performance of the application.

Call external API

Suppose we need to call an external API in a Spring Boot application, get some data, and save it to the database. If we call the API directly in the main thread, then we need to wait for the API's response before we can continue to execute subsequent logic. This will waste the resources of the main thread and also affect the user experience. To avoid this, we can use @Asyncannotations to mark an asynchronous method so that it executes in a separate thread. For example:

@Service
public class ApiService {
    
    

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private DataRepository dataRepository;

    @Async
    public void callApiAndSaveData() {
    
    
        // 调用外部的API
        Data data = restTemplate.getForObject("https://example.com/api", Data.class);
        // 保存数据到数据库
        dataRepository.save(data);
    }
}

In the above code, we use @Serviceannotations to define a service class and ApiServiceinject two components. It is a tool class used to send HTTP requests and an interface used to operate the database. Then we define a method and use annotations to mark it as an asynchronous method. In this way, when we call this method, it will be executed in a new thread without blocking the main thread.RestTemplateDataRepositoryRestTemplateDataRepositorycallApiAndSaveData()@Async

To use @Asyncannotations, we also need to enable async support in the Spring Boot application. We can add annotations to the configuration class @EnableAsyncto achieve this. For example:

@Configuration
@EnableAsync
public class AsyncConfig {
    
    
}

Perform complex calculations

Suppose we need to perform some complex calculations in a Spring Boot application, such as calculating the factorial of a large number. If we perform calculations directly in the main thread, then we need to wait for the calculation results before continuing to execute subsequent logic. This will occupy the CPU resources of the main thread and also affect the user experience. To avoid this, we can use CompletableFuturea class to create an asynchronous task and return a future result. For example:

@Service
public class MathService {
    
    

    public CompletableFuture<BigInteger> factorial(BigInteger n) {
    
    
        // 创建一个异步任务
        return CompletableFuture.supplyAsync(() -> {
    
    
            // 执行复杂的计算
            BigInteger result = BigInteger.ONE;
            for (BigInteger i = BigInteger.ONE; i.compareTo(n) <= 0; i = i.add(BigInteger.ONE)) {
    
    
                result = result.multiply(i);
            }
            return result;
        });
    }
}

In the above code, we use @Serviceannotations to define a service class MathService. Then we define a method factorial()that accepts a large number as a parameter and returns a future result. We used CompletableFuture.supplyAsync()the method to create an asynchronous task and passed in a lambda expression to define the calculation logic. This way when we call this method, it will return an CompletableFutureobject immediately without blocking the main thread. We can use the methods of this object elsewhere to obtain the calculation results, or add callback functions to handle the calculation results. For example:

@Controller
public class MathController {
    
    

    @Autowired
    private MathService mathService;

    @GetMapping("/factorial")
    public String factorial(@RequestParam("n") BigInteger n, Model model) {
    
    
        // 调用异步方法
        CompletableFuture<BigInteger> future = mathService.factorial(n);
        // 添加回调函数
        future.thenAccept(result -> {
    
    
            // 将计算结果添加到模型中
            model.addAttribute("result", result);
        });
        // 返回视图名称
        return "factorial";
    }
}

In the above code, we use @Controllerannotations to define a controller class MathControllerand inject MathServicethe component. Then we define a method factorial()that accepts a request parameter nand returns a view name. We use @GetMappingannotations to map a GET request to this method. In the method, we called MathServicethe asynchronous method factorial()and got a future result. We then used CompletableFuture.thenAccept()methods to add a callback function that accepts a lambda expression to define the processing logic. In this logic, we add the calculation results to the model for display in the view.

Process large amounts of data

Suppose we need to process some large amounts of data in a Spring Boot application, such as reading data from a file and performing some analysis and transformation. If we process data directly in the main thread, then we need to wait for the data processing to be completed before we can continue to execute subsequent logic. This will occupy the memory resources of the main thread and also affect the user experience. To avoid this, we can use Stream APIto create a parallel stream and take advantage of multi-core CPUs to speed up data processing. For example:

@Service
public class DataService {
    
    

    public void processData(String fileName) {
    
    
        // 创建一个并行流
        try (Stream<String> lines = Files.lines(Paths.get(fileName)).parallel()) {
    
    
            // 对每一行数据进行分析和转换
            lines.map(line -> analyzeAndTransform(line))
                // 对转换后的数据进行汇总和输出
                .collect(Collectors.groupingBy(data -> data.getType(), Collectors.counting()))
                .forEach((type, count) -> System.out.println(type + ": " + count));
        } catch (IOException e) {
    
    
            e.printStackTrace();
        }
    }

    private Data analyzeAndTransform(String line) {
    
    
        // 省略具体的分析和转换逻辑
        return new Data();
    }
}

In the above code, we use @Serviceannotations to define a service class DataService. Then we define a method processData()that accepts a filename as a parameter. In the method, we used Files.lines()the method to create a stream that can read the data from the file line by line. We then used Stream.parallel()methods to convert the stream into a parallel stream, which allows multiple threads to process elements in the stream at the same time. Next, we use a series of stream operations to analyze and transform each row of data, and summarize and output the transformed data.

How to optimize multi-threaded performance

When developing in Spring Boot, using multi-threading can improve the efficiency and performance of your application, but there are also some issues and risks that you need to be aware of. If multi-threading is used improperly, it may cause some problems, such as deadlock, race conditions, memory leaks, etc. In order to avoid these problems and optimize multi-threaded performance, we can follow some principles and techniques.

Guess you like

Origin blog.csdn.net/m0_61581389/article/details/132595539