线程池是线程的一个缓存,可以提供线程重用,避免了线程重复地进行创建销毁,更加科学地管理线程,对线程进行统一地优化,调度和监控。
适用于 任务时间短、任务量大。
如果一个线程任务时间非常 长,可以忽略到线程的创建和销毁时间,那么使用线程池反而不合适
阻塞队列 在任意时刻,不管并发有多高,只有一个一个线程能够进行队列的入队或者出队操作。线程安全。 队列满,只能进行出队,入队被阻塞。 队列空,只能进行入队,出队被阻塞。
线程池按以下行为执行任务
当线程数小于核心线程数时,创建线程。当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列。当线程数大于等于核心线程数,且任务队列已满 若线程数小于最大线程数,创建线程 若线程数等于最大线程数,抛出异常,拒绝任务 因此并不能保证先来的先执行Running 能接受新任务以及处理已经添加的任务 Shutdown 不接受新任务,但可以处理已经添加的任务 Stop 不接受新任务,不处理已经添加的任务,并且中断正在执行的任务 Tidying 所有任务已经终止 Terminated 线程池彻底终止
Spring中实现多线程,其实非常简单,只需要在配置类中添加@EnableAsync就可以使用多线程。在希望执行的并发方法中使用@Async就可以定义一个线程任务。通过spring给我们提供的ThreadPoolTaskExecutor就可以使用线程池。 线程池ThreadPoolExecutor,它的执行规则如下
在Springboot中对其进行了简化处理,只需要配置一个类型为java.util.concurrent.TaskExecutor或其子类的bean,并在配置类或直接在程序入口类上声明注解@EnableAsync。 调用也简单,在由Spring管理的对象的方法上标注注解@Async,显式调用即可生效。
例子:
@Configuration @EnableAsync // 启用异步任务 public class AsyncConfiguration {
// 声明一个线程池(并指定线程池的名字) @Bean("taskExecutor") public Executor asyncExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); //核心线程数5:线程池创建时候初始化的线程数 executor.setCorePoolSize(5); //最大线程数5:线程池最大的线程数,只有在缓冲队列满了之后才会申请超过核心线程数的线程 executor.setMaxPoolSize(5); //缓冲队列500:用来缓冲执行任务的队列 executor.setQueueCapacity(500); //允许线程的空闲时间60秒:当超过了核心线程出之外的线程在空闲时间到达之后会被销毁 executor.setKeepAliveSeconds(60); //线程池名的前缀:设置好了之后可以方便我们定位处理任务所在的线程池 executor.setThreadNamePrefix("DailyAsync-"); executor.initialize(); return executor; }} 有很多可以配置的东西。默认情况下,使用SimpleAsyncTaskExecutor
在定义了线程池之后,我们只需要在@Async注解中指定线程池名即可,比如: 例子:
```java @Service public class GitHubLookupService { private static final Logger logger = LoggerFactory.getLogger(GitHubLookupService.class); @Autowired private RestTemplate restTemplate; // 这里进行标注为异步任务,在执行此方法的时候,会单独开启线程来执行(并指定线程池的名字) @Async("taskExecutor") public CompletableFuture<String> findUser(String user) throws InterruptedException { logger.info("Looking up " + user); String url = String.format("https://api.github.com/users/%s", user); String results = restTemplate.getForObject(url, String.class); // Artificial delay of 3s for demonstration purposes Thread.sleep(3000L); return CompletableFuture.completedFuture(results); } } findUser 方法被标记为Spring的 @Async 注解,表示它将在一个单独的线程上运行。该方法的返回类型是 CompleetableFuture 而不是 String,这是任何异步服务的要求。 #### 高并发下线程池配置 线程池大小配置 一般根据任务类型进行区分, 假设CPU为N核 CPU密集型任务需要减少线程数量, 降低线程切换开销,可配置线程池大小为N + 1. IO密集型任务则可以加大线程数量, 可配置线程池大小为 N * 2. 混合型任务则可以拆分为CPU密集型与IO密集型, 独立配置. CPU密集型: 例如,一般我们系统的静态资源,比如js,css等,会存在一个版本号,如 main.js?v0,每当用户访问这个资源的时候,会发送一个比对请求到服务端,比对本地静态文件版本和服务端的文件版本是否一致,不一致则更新.这种任务一般不占用大量IO,所以后台服务器可以快速处理,压力落在CPU上. I/O密集型 1个线程对应1个方法栈,线程的生命周期与方法栈相同. 比如某个线程的方法栈对应的入站顺序为:controller()->service()->DAO(),由于DAO长时间的I/O操作,导致该线程一直处于工作队列,但它又不占用CPU,则此时有1个CPU是处于空闲状态的. 所以,这种情况下,应该加大线程池工作队列的长度(如果CPU调度算法使用的是FCFS,则无法切换),尽量不让CPU空闲下来,提高CPU利用率. ## 实战 启动类添加@EnableAsync , 表示开启异步任务 使用@Configuration 配置一个线程池 @Configuration @EnableAsync public class AsynConfig { @Bean("taskExecutor") public Executor asyncExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); //核心线程数5:线程池创建时候初始化的线程数 executor.setCorePoolSize(5); //最大线程数5:线程池最大的线程数,只有在缓冲队列满了之后才会申请超过核心线程数的线程 executor.setMaxPoolSize(5); //缓冲队列500:用来缓冲执行任务的队列 executor.setQueueCapacity(500); //允许线程的空闲时间60秒:当超过了核心线程出之外的线程在空闲时间到达之后会被销毁 executor.setKeepAliveSeconds(60); //线程池名的前缀:设置好了之后可以方便我们定位处理任务所在的线程池 executor.setThreadNamePrefix("DailyAsync-"); executor.initialize(); return executor; } }给异步方法@Async(“taskExcutor”)
@Async("taskExecutor") public Future<Map<String, Object>> query(User d, HttpServletRequest request) { // TODO Auto-generated method stub //省略逻辑 Map<String,Object> map = new HashMap<>(); return new AsyncResult<>(map); } 异步回调 给方法外面包一层 Future<> 返回值 AsyncResult 回调方法调用AsynResult.get()方法 get方法会阻塞到调用结束 resultMap.put("query", quary.get(10, TimeUnit.SECONDS)); ### 注意点 1.注解的方法必须是public方法。 2.方法一定要从另一个类中调用,也就是从类的外部调用,类的内部调用是无效的。 3.如果需要从类的内部调用,需要先获取其代理类,下面上代码 ```java @Service public class XxxService{ public void methodA(){ ... XxxService xxxServiceProxy = SpringUtil.getBean(XxxService.class); xxxServiceProxy.methodB(); ... } @Async public void methodB() { ... } }代理工具类我也贴在下面
@Component public class SpringBeanUtil implements ApplicationContextAware { private static ApplicationContext applicationContext; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { if (SpringBeanUtil.applicationContext == null) { SpringBeanUtil.applicationContext = applicationContext; } } public static ApplicationContext getApplicationContext(){ return applicationContext; } public static Object getBean(String name) { return applicationContext.getBean(name); } public static <T> T getBean(Class<T> clazz) { return applicationContext.getBean(clazz); } } ReportService beanProxy = SpringBeanUtil.getBean(ReportService.class); Future<JSONObject> reportAsync = beanProxy.getReportAsync(patCardType, patCardNo, beginDate, endDate);这样就可以在同一个类中调用异步方法