springboot中线程池使用

线程池实现方式

在Spring Boot开发中,线程池的使用可以提高应用程序的性能和响应能力。以下是几种常见的线程池使用方法及其示例。

1. 使用@Async注解

Spring提供了@Async注解,可以轻松地将方法异步执行。

  1. @SpringBootApplication启动类当中没有添加@EnableAsync注解。
  2. 异步方法使用注解@Async的返回值只能为void或者Future。
  3. 没有走Spring的代理类。因为@Transactional和@Async注解的实现都是基于Spring的AOP,而AOP的实现是基于动态代理模式实现的。那么注解失效的原因就很明显了,有可能因为调用方法的是

解决方法:
这里具体说一下第三种情况的解决方法。

  1. 注解的方法必须是public方法。
  2. 注解的方法不要定义为static
  3. 方法一定要从另一个类中调用,也就是从类的外部调用,类的内部调用是无效的。
  4. 如果需要从类的内部调用,需要先获取其代理类。
    当使用@Async时,默认情况下,Spring会创建一个简单的TaskExecutor(通常是SimpleAsyncTaskExecutor),它为每个任务分配一个独立的线程。然而,在生产环境中,这种模式可能不是最有效的,因为它可能会导致过多的线程被创建,从而影响系统性能或稳定性。
点击查看代码

import org.springframework.scheduling.annotation.Async;  
import org.springframework.scheduling.annotation.EnableAsync;  
import org.springframework.stereotype.Service;  

@Service  
@EnableAsync  
public class AsyncService {  

    @Async  
    public void asyncMethod() {  
        // 模拟耗时操作  
        try {  
            Thread.sleep(2000);  
            System.out.println("异步方法执行完成");  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
    }  
}  

2. 自定义线程池

可以通过ThreadPoolTaskExecutor自定义线程池。

单线程池

点击查看代码
import org.springframework.context.annotation.Bean;  
import org.springframework.context.annotation.Configuration;  
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;  

@Configuration  
public class ThreadPoolConfig {  

    @Bean("taskExecutor")
    public ThreadPoolTaskExecutor taskExecutor() {  
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();  
        executor.setCorePoolSize(5);  
        executor.setMaxPoolSize(10);  
        executor.setQueueCapacity(25);  
        executor.setThreadNamePrefix("MyExecutor-");  
        executor.initialize();  
        return executor;  
    }
}
问题笔记
点击查看代码

springboot项目中如何使用自定义线程池?

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

@Configuration  
public class ThreadPoolConfig {  

    @Bean  
    public ThreadPoolTaskExecutor taskExecutor() {  
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();  
        executor.setCorePoolSize(5); // 核心线程数  
        executor.setMaxPoolSize(10); // 最大线程数  
        executor.setQueueCapacity(25); // 队列容量  
        executor.setThreadNamePrefix("MyExecutor-"); // 线程名称前缀  
        executor.initialize();  
        return executor;  
    }  
}
``` java
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.scheduling.annotation.Async;  
import org.springframework.stereotype.Service;  

@Service  
public class MyService {  

    @Autowired  
    private ThreadPoolTaskExecutor taskExecutor;  

    @Async  
    public void executeTask(String taskName) {  
        System.out.println("Executing task: " + taskName + " by " + Thread.currentThread().getName());  
        // 模拟任务执行  
        try {  
            Thread.sleep(2000);  
        } catch (InterruptedException e) {  
            Thread.currentThread().interrupt();  
        }  
        System.out.println("Task " + taskName + " completed by " + Thread.currentThread().getName());  
    }  
}
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.web.bind.annotation.GetMapping;  
import org.springframework.web.bind.annotation.RestController;  

@RestController  
public class MyController {  

    @Autowired  
    private MyService myService;  

    @GetMapping("/start-tasks")  
    public String startTasks() {  
        for (int i = 1; i <= 10; i++) {  
            myService.executeTask("Task " + i);  
        }  
        return "Tasks started!";  
    }  
}

@Async,这意味着Spring会自动使用配置的线程池来执行这个方法,而不需要显式调用taskExecutor。当然也可以使用显式调用方式执行这个方法,以下为示例代码;

import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.scheduling.annotation.Async;  
import org.springframework.stereotype.Service;  

@Service  
public class MyService {  

    @Autowired  
    private ThreadPoolTaskExecutor taskExecutor;  

    public void executeTask(String taskName) {  
        taskExecutor.execute(() -> {  
            System.out.println("Executing task: " + taskName + " by " + Thread.currentThread().getName());  
            // 模拟任务执行  
            try {  
                Thread.sleep(2000);  
            } catch (InterruptedException e) {  
                Thread.currentThread().interrupt();  
            }  
            System.out.println("Task " + taskName + " completed by " + Thread.currentThread().getName());  
        });  
    }  
}

多线程池

点击查看代码
@Configuration
public class ThreadPoolConfig {

       @Bean("taskExecutor")
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        //设置线程池参数信息
        taskExecutor.setCorePoolSize(10);
        taskExecutor.setMaxPoolSize(50);
        taskExecutor.setQueueCapacity(200);
        taskExecutor.setKeepAliveSeconds(60);
        taskExecutor.setThreadNamePrefix("myExecutor--");
        taskExecutor.setWaitForTasksToCompleteOnShutdown(true);
        taskExecutor.setAwaitTerminationSeconds(60);
        //修改拒绝策略为使用当前线程执行
        taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        //初始化线程池
        taskExecutor.initialize();
        return taskExecutor;
    }

    @Bean("poolExecutor")
    public Executor poolExecutor() {
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        //设置线程池参数信息
        taskExecutor.setCorePoolSize(10);
        taskExecutor.setMaxPoolSize(50);
        taskExecutor.setQueueCapacity(200);
        taskExecutor.setKeepAliveSeconds(60);
        taskExecutor.setThreadNamePrefix("myExecutor2--");
        taskExecutor.setWaitForTasksToCompleteOnShutdown(true);
        taskExecutor.setAwaitTerminationSeconds(60);
        //修改拒绝策略为使用当前线程执行
        taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        //初始化线程池
        taskExecutor.initialize();
        return taskExecutor;
    }

    @Bean("taskPoolExecutor")
    public Executor taskPoolExecutor() {
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        //设置线程池参数信息
        taskExecutor.setCorePoolSize(10);
        taskExecutor.setMaxPoolSize(50);
        taskExecutor.setQueueCapacity(200);
        taskExecutor.setKeepAliveSeconds(60);
        taskExecutor.setThreadNamePrefix("myExecutor3--");
        taskExecutor.setWaitForTasksToCompleteOnShutdown(true);
        taskExecutor.setAwaitTerminationSeconds(60);
        //修改拒绝策略为使用当前线程执行
        taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        //初始化线程池
        taskExecutor.initialize();
        return taskExecutor;
    }
}

引用:

  1. 注入
@Autowired
ThreadPoolTaskExecutor commonTaskExecutor; //会去匹配 @Bean("commonTaskExecutor") 这个线程池
  1. 注解@Async
    如果是使用的@Async注解,只需要在注解里面指定bean的名称就可以切换到对应的线程池去了。如下所示:
@Async("commonTaskExecutor")
public void hello(String name){
    logger.info("异步线程启动 started."+name);
}

3. 使用ExecutorService

可以直接使用Java的ExecutorService来创建线程池。

根据阿里巴巴规范不推荐使用,易导致OOM
示例代码:

点击查看代码

import org.springframework.stereotype.Service;  

import java.util.concurrent.ExecutorService;  
import java.util.concurrent.Executors;  

@Service  
public class ExecutorServiceExample {  

    private final ExecutorService executorService = Executors.newFixedThreadPool(5);  

    public void executeTask() {  
        executorService.submit(() -> {  
            // 模拟耗时操作  
            try {  
                Thread.sleep(2000);  
                System.out.println("任务执行完成");  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            }  
        });  
    }  
}  

4. 使用CompletableFuture

CompletableFuture提供了更强大的异步编程能力。

点击查看代码

import org.springframework.stereotype.Service;  

import java.util.concurrent.CompletableFuture;  

@Service  
public class CompletableFutureExample {  

    public CompletableFuture<String> asyncTask() {  
        return CompletableFuture.supplyAsync(() -> {  
            // 模拟耗时操作  
            try {  
                Thread.sleep(2000);  
                return "任务完成";  
            } catch (InterruptedException e) {  
                throw new IllegalStateException(e);  
            }  
        });  
    }  
}  

5.使用插件hutool创建线程池

使用Hutool的线程池可以简化线程管理,并且能够根据实际需求更灵活地配置线程池参数。大致与自定义线程池一致,下面是如何在Spring Boot项目中集成和使用Hutool线程池的基本步骤

点击查看代码
import cn.hutool.core.thread.ThreadPoolUtil;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ThreadPoolConfig {

    @Bean
    public ExecutorService threadPool() {
        // 使用Hutool提供的工具类创建一个线程池
        return ThreadPoolUtil.createThreadPool(5, 10,
                2000, TimeUnit.MILLISECONDS,
                200, new LinkedBlockingQueue<>(1000),
                new NamedThreadFactory("my-thread-pool", true),
                new ThreadPoolExecutor.CallerRunsPolicy());
    }
}

使用:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class MyService {

    private final ExecutorService executorService;

    @Autowired
    public MyService(ExecutorService executorService) {
        this.executorService = executorService;
    }

    public void executeTask(Runnable task) {
        executorService.execute(task);
    }
}

注意事项

  • 确保线程池关闭:在Spring Boot应用停止时,要确保线程池正确关闭以释放资源。可以通过实现DisposableBean接口或者使用@PreDestroy注解的方法来完成这一点。
  • 监控与调整:根据应用的实际运行情况,可能需要调整线程池的参数以获得最佳性能。
  • 异常处理:考虑在提交任务给线程池时如何处理可能出现的异常(例如RejectedExecutionException)。

延申开发遇到问题

观察以下代码,实现效果有何不同。

点击查看代码
private final ExecutorService executorService = Executors.newFixedThreadPool(5);  

    public void executeTask() {  
        executorService.submit(() -> {  
            // 模拟耗时操作  
            try {  
                Thread.sleep(2000);  
                System.out.println("任务执行完成");  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            }  
        });  
    }  

    public void executeTask() {  
		ExecutorService executorPool = Executors.newSingleThreadExecutor();
        executorPool.submit(() -> {  
            // 模拟耗时操作  
            try {  
                Thread.sleep(2000);  
                System.out.println("任务执行完成");  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            }  
        });  
    }

在这两段代码中,使用的线程池类型不同,导致它们在处理任务时的效率和行为也有所不同

第一段代码:

  • 使用固定大小的线程池
  • 线程池类型:newFixedThreadPool(5)创建了一个固定大小的线程池,最多可以同时处理5个任务。
  • 并发能力:可以同时处理多个任务,适合高并发场景。
  • 资源利用:在有多个用户同时调用executeTask()时,最多可以同时执行5个任务,其他任务会被放入队列中等待执行。
  • 效率:在高并发情况下,效率较高,因为可以同时处理多个任务。

第二段代码:

  • 使用单线程池
  • 线程池类型:newSingleThreadExecutor()创建了一个单线程池,只能同时处理一个任务。
  • 并发能力:只能顺序处理任务,无法并行执行。
  • 资源利用:在多个用户同时调用executeTask()时,所有任务会被排队,依次执行,导致响应时间增加。
  • 效率:在高并发情况下,效率较低,因为只能处理一个任务,其他任务需要等待。

效率比较

高并发场景:在多个用户同时调用executeTask()的情况下,第一段代码(固定线程池)能够更好地利用系统资源,提供更高的并发处理能力,因此效率更高。
低并发场景:如果只有少量用户调用,第二段代码(单线程池)可能在简单场景下表现良好,但在实际应用中,通常会遇到性能瓶颈。

在Spring Boot整个系统中,第一段代码使用的固定线程池在处理多个用户同时调用的情况下效率更高,能够更好地利用系统资源并提高响应速度。而第二段代码由于使用单线程池,无法并行处理任务,效率较低,适合任务量较小且不需要并发处理的场景。

posted @ 2024-09-09 17:40  codeplay的园子  阅读(121)  评论(0)    收藏  举报