Spring提供的线程池支持
- 格式:docx
- 大小:25.60 KB
- 文档页数:4
threadpooltaskexecutor 总结-回复threadPoolTaskExecutor是一个用于执行多线程任务的线程池,它属于Spring框架的线程池实现类。
本文将详细介绍threadPoolTaskExecutor的概念、使用方法和一些最佳实践,以帮助读者全面了解和灵活运用该线程池。
一、threadPoolTaskExecutor简介threadPoolTaskExecutor是Spring框架中的一个用于执行多线程任务的线程池实现类。
它继承了ThreadPoolExecutor并实现了AsyncTaskExecutor、TaskExecutor和SchedulingTaskExecutor等接口,提供了一系列方法用于配置和管理线程池的行为。
threadPoolTaskExecutor支持并发执行任务,提供了线程池的管理和线程任务的执行等功能。
二、使用threadPoolTaskExecutor的方法1. 引入threadPoolTaskExecutor依赖要使用threadPoolTaskExecutor,首先需要在项目的配置文件(如pom.xml文件)中引入相应的依赖:<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency>2. 配置threadPoolTaskExecutor在Spring配置文件(如application.properties或application.yaml)中配置threadPoolTaskExecutor的相关属性。
可以根据实际需求设置以下属性:- corePoolSize:核心线程池大小,即保持活跃的线程数量。
- maxPoolSize:最大线程池大小,即线程池中最多能同时执行的线程数量。
javaspringboot线程池以及多线程线程和进程进程是资源分配的最⼩单位,线程是CPU调度的最⼩单位。
是不是很抽象,做个简单⽐喻,进程=⽕车,线程=车厢,线程在进程⾥运⾏(单个的车厢是⽆法运⾏的);不同进程之间数据很难共享,同⼀进程下的线程数据共享则很容易。
多线程⼀个应⽤程序有多条执⾏路径(单线程:⼀个应⽤程序只有⼀条执⾏路径)。
应⽤场景异步,有些功能⽆需同步执⾏,可以使⽤另外⼀个线程去执⾏。
多个线程共同完成⼀个事情,缩短整体执⾏时间,同时cpu也得到了充分利⽤(例如,盖房⼦垒墙,⼀个⼈需要10天,10个⼈同时做,1天左右可以完成)。
线程池什么是线程池线程池,顾名思义,是存放了⼀堆线程的池⼦/容器,并且管理线程的整个⽣命周期。
java.util.concurrent.Executors提供了⼀个 java.util.concurrent.Executor接⼝的实现⽤于创建线程池。
为什么要⽤线程池1. 减少了创建和销毁线程的次数,每个⼯作线程都可以被重复利⽤,可执⾏多个任务。
2. 可以根据系统的承受能⼒,调整线程池中⼯作线线程的数⽬,防⽌因为消耗过多的内存,⽽把服务器累趴下(每个线程需要⼤约1MB内存,线程开的越多,消耗的内存也就越⼤,最后死机)。
实现⽅式此处以springboot实现⽅式为例import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.scheduling.annotation.EnableAsync;import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;import java.util.concurrent.Executor;import java.util.concurrent.ThreadPoolExecutor;/*** @author lipeiguang*/@Configuration@EnableAsyncpublic class ThreadPoolConfig {@Bean(name = "executor")public Executor getAsyncThread() {ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();//线程池维护线程的最少数量taskExecutor.setCorePoolSize(5);//线程池维护线程的最⼤数量,只有在缓冲队列满了之后才会申请超过核⼼线程数的线程taskExecutor.setMaxPoolSize(10);//缓存队列taskExecutor.setQueueCapacity(20);//允许的空闲时间,当超过了核⼼线程出之外的线程在空闲时间到达之后会被销毁taskExecutor.setKeepAliveSeconds(200);//线程名称前缀taskExecutor.setThreadNamePrefix("my-thread-");// 线程池对拒绝任务(⽆线程可⽤)的处理策略,⽬前只⽀持AbortPolicy、CallerRunsPolicy;默认为后者taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());// 调度器shutdown被调⽤时等待当前被调度的任务完成taskExecutor.setWaitForTasksToCompleteOnShutdown(true);// 等待时长taskExecutor.setAwaitTerminationSeconds(60);taskExecutor.initialize();return taskExecutor;}}其中的corePoolSize设置参考:N为cpu个数如果是CPU密集型应⽤,则线程池⼤⼩设置为N+1如果是IO密集型应⽤,则线程池⼤⼩设置为2N+1cpu个数查询⽅式:Runtime.getRuntime().availableProcessors();linux查询⽅式:cat /proc/cpuinfo| grep"processor"| wc -l使⽤线程池实现异步import io.swagger.annotations.Api;import io.swagger.annotations.ApiOperation;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RestController;import javax.annotation.Resource;import java.util.concurrent.Executor;/*** 使⽤demo* @author lipeiguang*/@Slf4j@Api(tags = {"异步线程测试"})@RestControllerpublic class ExecutorDemo {//使⽤时通过注解/*** 如果是多线程,可以直接使⽤这个executor*/@Resource(name = TaskExecutorConstant.SALARY_TASK_EXECUTOR) private Executor executor;@Autowiredprivate ServiceDemo serviceDemo;@ApiOperation("异步测试")@GetMapping("/test/async")public String testAsync(){("主线程开始执⾏。
Spring中的线程池和定时任务功能1.功能介绍Spring框架提供了线程池和定时任务执⾏的抽象接⼝:TaskExecutor和TaskScheduler来⽀持异步执⾏任务和定时执⾏任务功能。
同时使⽤框架⾃⼰定义的抽象接⼝来屏蔽掉底层JDK版本间以及Java EE中的线程池和定时任务处理的差异。
另外Spring还⽀持集成JDK内部的定时器Timer和Quartz Scheduler框架。
2.线程池的抽象:TaskExecutorTaskExecutor涉及到的相关类图如下:TaskExecutor接⼝源代码如下所⽰:public interface TaskExecutor extends Executor {/*** Execute the given {@code task}.* <p>The call might return immediately if the implementation uses* an asynchronous execution strategy, or might block in the case* of synchronous execution.* @param task the {@code Runnable} to execute (never {@code null})* @throws TaskRejectedException if the given task was not accepted*/@Overridevoid execute(Runnable task);}此接⼝和Executor⼏乎完全⼀样,只定义了⼀个接收Runnable参数的⽅法,据Spring官⽅介绍此接⼝最初是为了在其他组建中使⽤线程时,将JKD抽离出来⽽设计的。
在Spring的⼀些其他组件中⽐如ApplicationEventMulticaster,Quartz都是使⽤TaskExecutor来作为线程池的抽象的。
线程池的参数配置线程池是一种用来管理多个线程的技术,可以显著提高多线程程序的性能和效率。
合理的线程池参数配置对于实现高并发和提高系统的稳定性至关重要。
本文将介绍线程池的常见参数配置,并提供一些最佳实践的建议。
1. 核心线程数(corePoolSize):核心线程数是线程池初始化时创建的线程数量。
这些线程处于一直活动的状态,并且在线程空闲时也不会被销毁。
核心线程数的设置需要根据系统的负载情况和处理能力进行调整。
一般来说,可以按照CPU核心数来设置核心线程数,在4-8之间选择一个适当的值。
2. 最大线程数(maximumPoolSize):最大线程数是线程池的最大容量,表示线程池中最多能创建多少个线程。
在核心线程都处于运行状态且工作队列已满时,线程池会创建新的线程,直到达到最大线程数。
最大线程数需要根据系统的负载情况和内存资源来进行设置。
3. 空闲线程存活时间(keepAliveTime):空闲线程存活时间指的是当线程池中的线程数量大于核心线程数且没有可处理的任务时,多余的空闲线程在被销毁前的等待时间。
如果线程在指定的时间内没有任务可执行,那么它将被销毁。
可以根据任务的平均执行时间和系统响应时间来进行设置,一般建议设置为1-5分钟。
4. 工作队列(workQueue):工作队列用来存放待执行的任务。
线程池中的线程会从工作队列中获取任务进行执行。
Java提供了多种类型的工作队列,如ArrayBlockingQueue、LinkedBlockingQueue等,选择适合系统需求的队列类型,可以根据任务量和性能需求来选择。
5. 拒绝策略(RejectedExecutionHandler):当线程池中的线程数量达到最大线程数且工作队列已满时,新提交的任务将会被拒绝执行。
拒绝策略用来定义当线程池饱和时如何处理新的任务。
Java提供了几种默认的拒绝策略,如AbortPolicy、CallerRunsPolicy等。
Springboot之多线程多任务并⾏+线程池处理最近项⽬中做到⼀个关于批量发短信的业务,如果⽤户量特别⼤的话,不能使⽤单线程去发短信,只能尝试着使⽤多任务来完成!我们的项⽬使⽤到了⽅式⼆,即Future的⽅案Java 线程池Java通过Executors提供四种线程池,分别为:newCachedThreadPool创建⼀个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若⽆可回收,则新建线程。
newFixedThreadPool 创建⼀个定长线程池,可控制线程最⼤并发数,超出的线程会在队列中等待。
newScheduledThreadPool 创建⼀个定长线程池,⽀持定时及周期性任务执⾏。
newSingleThreadExecutor 创建⼀个单线程化的线程池,它只会⽤唯⼀的⼯作线程来执⾏任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执⾏。
优点重⽤存在的线程,减少对象创建、消亡的开销,性能佳。
可有效控制最⼤并发线程数,提⾼系统资源的使⽤率,同时避免过多资源竞争,避免堵塞。
提供定时执⾏、定期执⾏、单线程、并发数控制等功能。
⽅式⼀(CountDownLatch)public class StatsDemo {final static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");final static String startTime = sdf.format(new Date());/*** IO密集型任务 = ⼀般为2*CPU核⼼数(常出现于线程中:数据库数据交互、⽂件上传下载、⽹络数据传输等等)* CPU密集型任务 = ⼀般为CPU核⼼数+1(常出现于线程中:复杂算法)* 混合型任务 = 视机器配置和复杂度⾃测⽽定*/private static int corePoolSize = Runtime.getRuntime().availableProcessors();/*** public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,* TimeUnit unit,BlockingQueue<Runnable> workQueue)* corePoolSize⽤于指定核⼼线程数量* maximumPoolSize指定最⼤线程数* keepAliveTime和TimeUnit指定线程空闲后的最⼤存活时间* workQueue则是线程池的缓冲队列,还未执⾏的线程会在队列中等待* 监控队列长度,确保队列有界* 不当的线程池⼤⼩会使得处理速度变慢,稳定性下降,并且导致内存泄露。
Spring线程池配置Spring通过ThreadPoolTaskExecutor实现线程池技术,它是使⽤jdk中的Java.util.concurrent.ThreadPoolExecutor进⾏实现。
Spring 配置线程池,有两种⽅式:⽅式⼀:XML定义bean<!-- 包路径扫描 --><context:component-scan base-package="spring.task"/><!-- Spring线程池 --><bean id="taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor"><!-- 核⼼线程数 --><property name="corePoolSize" value="5" /><!-- 线程池维护线程的最⼤数量 --><property name="maxPoolSize" value="10" /><!-- 允许的空闲时间, 默认60秒 --><property name="keepAliveSeconds" value="60" /><!-- 任务队列 --><property name="queueCapacity" value="50" /><!-- 线程超过空闲时间限制,均会退出直到线程数量为0 --><property name="allowCoreThreadTimeOut" value="true"/><!-- 对拒绝task的处理策略 --><property name="rejectedExecutionHandler"><bean class="java.util.concurrent.ThreadPoolExecutor.DiscardOldestPolicy" /></property></bean>当⼀个新任务来临时:1)如果此时线程池中的数量⼩于corePoolSize,即使线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务;2)如果此时线程池中的数量等于corePoolSize,但是缓冲队列workQueue未满,那么任务被放⼊缓冲队列;3)如果此时线程池中的数量⼤于corePoolSize,缓冲队列workQueue满,并且线程池中的数量⼩于maxPoolSize,建新的线程来处理被添加的任务;4)如果此时线程池中的数量⼤于corePoolSize,缓冲队列workQueue满,并且线程池中的数量等于maxPoolSize,那么通过handler所指定的策略来处理此任务;5)当线程池中的线程数量⼤于corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终⽌,如果allowCoreThreadTimeOut为false,则线程数量维持在corePoolSize,如果为true,则线程数量可最低降⾄0;⽅式⼆:使⽤task:executor⽅式<?xml version="1.0" encoding="UTF-8"?><beans xmlns="/schema/beans"xmlns:xsi="/2001/XMLSchema-instance"xmlns:task="/schema/task"xsi:schemaLocation="/schema/beans /schema/beans/spring-beans.xsd/schema/task /schema/task/spring-task.xsd"><!-- 包路径扫描 --><context:component-scan base-package="spring.task"/><!-- 定义线程池 --><task:executor id="executor" pool-size="5" queue-capacity="10" rejection-policy="DISCARD_OLDEST"/></beans>测试代码@Componentpublic class AsyncTask {@Autowiredprivate ThreadPoolTaskExecutor executor;@PostConstructpublic void testAsycTask() {for (int i = 0; i < 10; i++) {executor.execute(new Runnable() {public void run() {asyncTask();}});}}private void asyncTask(){System.out.println( "---" + Thread.currentThread().getName());}}测试结果---executor-1---executor-2---executor-3---executor-4---executor-5---executor-6---executor-7---executor-8---executor-9---executor-10。
Spring使⽤@Async注解,多线程@Async注解的原理及使⽤本⽂讲述@Async注解,在Spring体系中的应⽤。
本⽂仅说明@Async注解的应⽤规则,对于原理,调⽤逻辑,源码分析,暂不介绍。
对于异步⽅法调⽤,从Spring3开始提供了@Async注解,该注解可以被标注在⽅法上,以便异步地调⽤该⽅法。
调⽤者将在调⽤时⽴即返回,⽅法的实际执⾏将提交给Spring TaskExecutor的任务中,由指定的线程池中的线程执⾏。
在项⽬应⽤中,@Async调⽤线程池,推荐使⽤⾃定义线程池的模式。
⾃定义线程池常⽤⽅案:重新实现接⼝AsyncConfigurer。
简介应⽤场景同步:同步就是整个处理过程顺序执⾏,当各个过程都执⾏完毕,并返回结果。
异步:异步调⽤则是只是发送了调⽤的指令,调⽤者⽆需等待被调⽤的⽅法完全执⾏完毕;⽽是继续执⾏下⾯的流程。
例如,在某个调⽤中,需要顺序调⽤ A, B, C三个过程⽅法;如他们都是同步调⽤,则需要将他们都顺序执⾏完毕之后,⽅算作过程执⾏完毕;如B为⼀个异步的调⽤⽅法,则在执⾏完A之后,调⽤B,并不等待B完成,⽽是执⾏开始调⽤C,待C执⾏完毕之后,就意味着这个过程执⾏完毕了。
在Java中,⼀般在处理类似的场景之时,都是基于创建独⽴的线程去完成相应的异步调⽤逻辑,通过主线程和不同的业务⼦线程之间的执⾏流程,从⽽在启动独⽴的线程之后,主线程继续执⾏⽽不会产⽣停滞等待的情况。
Spring 已经实现的线程池1. SimpleAsyncTaskExecutor:不是真的线程池,这个类不重⽤线程,默认每次调⽤都会创建⼀个新的线程。
2. SyncTaskExecutor:这个类没有实现异步调⽤,只是⼀个同步操作。
只适⽤于不需要多线程的地⽅。
3. ConcurrentTaskExecutor:Executor的适配类,不推荐使⽤。
如果ThreadPoolTaskExecutor不满⾜要求时,才⽤考虑使⽤这个类。
线程池的四种创建方式线程池是一种常见的多线程处理技术,它可以有效地管理线程资源,提高程序的运行效率。
在Java中,线程池的创建方式有四种:通过ThreadPoolExecutor类手动创建、通过Executors类工厂方法创建、通过Spring框架创建和通过Guava库创建。
下面将逐一介绍这四种方式的具体实现。
一、手动创建ThreadPoolExecutor类ThreadPoolExecutor是Java中最基本的线程池实现类,可以手动设置线程池中核心线程数、最大线程数、任务队列大小等参数。
具体实现步骤如下:1. 创建ThreadPoolExecutor对象```ThreadPoolExecutor executor = newThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, TimeUnit.SECONDS, workQueue);```其中,corePoolSize表示核心线程数,maximumPoolSize表示最大线程数,keepAliveTime表示非核心线程的存活时间,workQueue 表示任务队列。
2. 执行任务```executor.execute(task);```其中,task为Runnable或Callable类型的任务。
3. 关闭线程池```executor.shutdown();```二、通过Executors类工厂方法创建Java提供了Executors工厂类来简化线程池的创建过程。
Executors 提供了多个静态方法来创建不同类型的线程池,如newFixedThreadPool()、newCachedThreadPool()等。
具体实现步骤如下:1. 创建ExecutorService对象```ExecutorService executor = Executors.newFixedThreadPool(nThreads); ```其中,nThreads表示线程池中的线程数。
在springboot中使⽤java线程池ExecutorService的讲解1. 认识java线程池1.1 在什么情况下使⽤线程池?1.单个任务处理的时间⽐较短2.需处理的任务的数量⼤1.2 使⽤线程池的好处:1.减少在创建和销毁线程上所花的时间以及系统资源的开销2.如不使⽤线程池,有可能造成系统创建⼤量线程⽽导致消耗完系统内存1.3 线程池包括以下四个基本组成部分:1、线程池管理器(ThreadPool):⽤于创建并管理线程池,包括创建线程池,销毁线程池,添加新任务;2、⼯作线程(PoolWorker):线程池中线程,在没有任务时处于等待状态,可以循环的执⾏任务;3、任务接⼝(Task):每个任务必须实现的接⼝,以供⼯作线程调度任务的执⾏,它主要规定了任务的⼊⼝,任务执⾏完后的收尾⼯作,任务的执⾏状态等;4、任务队列(taskQueue):⽤于存放没有处理的任务。
提供⼀种缓冲机制。
1.4 线程池的核⼼参数ThreadPoolExecutor 有四个构造⽅法,前三个都是调⽤最后⼀个(最后⼀个参数最全)public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue) {this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,Executors.defaultThreadFactory(), defaultHandler);}public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory) {this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,threadFactory, defaultHandler);}public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler) {this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,Executors.defaultThreadFactory(), handler);}// 都调⽤它public ThreadPoolExecutor(// 核⼼线程数int corePoolSize,// 最⼤线程数int maximumPoolSize,// 闲置线程存活时间long keepAliveTime,// 时间单位TimeUnit unit,// 线程队列BlockingQueue<Runnable> workQueue,// 线程⼯⼚ThreadFactory threadFactory,// 队列已满,⽽且当前线程数已经超过最⼤线程数时的异常处理策略RejectedExecutionHandler handler ) {if (corePoolSize < 0 ||maximumPoolSize <= 0 ||maximumPoolSize < corePoolSize ||keepAliveTime < 0)throw new IllegalArgumentException();if (workQueue == null || threadFactory == null || handler == null)throw new NullPointerException();this.corePoolSize = corePoolSize;this.maximumPoolSize = maximumPoolSize;this.workQueue = workQueue;this.keepAliveTime = unit.toNanos(keepAliveTime);this.threadFactory = threadFactory;this.handler = handler;}主要参数corePoolSize:核⼼线程数核⼼线程会⼀直存活,即使没有任务需要执⾏当线程数⼩于核⼼线程数时,即使有线程空闲,线程池也会优先创建新线程处理设置allowCoreThreadTimeout=true(默认false)时,核⼼线程会超时关闭maxPoolSize:最⼤线程数当线程数>=corePoolSize,且任务队列已满时。
基于SpringBoot集成线程池,实现线程的池的动态监控(超级详细,建议收藏)线程池的监控很重要,对于前⾯章节讲的动态参数调整,其实还是得依赖于线程池监控的数据反馈之后才能做出调整的决策。
还有就是线程池本⾝的运⾏过程对于我们来说像⼀个⿊盒,我们没办法了解线程池中的运⾏状态时,出现问题没有办法及时判断和预警。
对于监控这类的场景,核⼼逻辑就是要拿到关键指标,然后进⾏上报,只要能实时拿到这些关键指标,就可以轻松实现监控以及预警功能。
ThreadPoolExecutor中提供了以下⽅法来获取线程池中的指标。
getCorePoolSize():获取核⼼线程数。
getMaximumPoolSize:获取最⼤线程数。
getQueue():获取线程池中的阻塞队列,并通过阻塞队列中的⽅法获取队列长度、元素个数等。
getPoolSize():获取线程池中的⼯作线程数(包括核⼼线程和⾮核⼼线程)。
getActiveCount():获取活跃线程数,也就是正在执⾏任务的线程。
getLargestPoolSize():获取线程池曾经到过的最⼤⼯作线程数。
getTaskCount():获取历史已完成以及正在执⾏的总的任务数量。
除此之外,ThreadPoolExecutor中还提供了⼀些未实现的钩⼦⽅法,我们可以通过重写这些⽅法来实现更多指标数据的获取。
beforeExecute,在Worker线程执⾏任务之前会调⽤的⽅法。
afterExecute,在Worker线程执⾏任务之后会调⽤的⽅法。
terminated,当线程池从状态变更到TERMINATED状态之前调⽤的⽅法。
⽐如我们可以在beforeExecute⽅法中记录当前任务开始执⾏的时间,再到afterExecute⽅法来计算任务执⾏的耗时、最⼤耗时、最⼩耗时、平均耗时等。
线程池监控的基本原理我们可以通过Spring Boot提供的Actuator,⾃定义⼀个Endpoint来发布线程池的指标数据,实现线程池监控功能。
核心提示:一旦企业应用越来越复杂时(比如,基于流程服务器的EIS),它们对相关技术也提出了更高的要求。
在使用EJB 3.0组件技术开发企业应用过程中,它们能够享受到EJB容器提供的线程池、任务调度(@Timeout)服务。
现如今,运行于Web容器的Web应用、单独的桌面应用一旦企业应用越来越复杂时(比如,基于流程服务器的EIS),它们对相关技术也提出了更高的要求。
在使用EJB 3.0组件技术开发企业应用过程中,它们能够享受到EJB容器提供的线程池、任务调度(@Timeout)服务。
现如今,运行于Web容器的Web应用、单独的桌面应用也复杂到需要依赖于线程池、任务调度的这类服务,是时候实现贵族到平民的转变了。
过去,很多企业项目可能会自身实现这类底层的非功能性的服务。
从Java SE 5.0开始,线程池服务(即,java.util.concurrent包)已经内置到JDK中。
至于任务调度服务,其实,自从Java 2 SDK 1.3以来,Java 2就内置了用于任务调度的定时器(比如,java.util.Timer、javax.swing.Timer)。
开源领域的Quartz Scheduler正是能够提供企业级任务调度服务的使能技术,而且EJB 2.x也加强了任务调度的支持。
甚至,BEA同IBM合作开发了Timer and Work Manager for Application Servers技术规范(CommonJ),这是专门用来解决Java EE应用中的线程池、任务调度问题的。
另外,JMX规范中也定义了javax.management.timer.TimerMBean,开发者借助于它能够实现定时JMX通知。
针对上述各种线程池、任务调度支持,Spring 2.0提供了统一的客户视图、抽象,这使得应用根本不用理会底层的具体实现和机制。
本章将从分析Spring 2.0提供的线程池支持入手,并过渡到Spring 2.0对任务调度提供的支持当中,从而进入到下一章内容。
15.1 Spring提供的线程池支持自从Spring 2.0开始,TaskExecutor接口被引入到Spring平台中,这主要受到Java SE 5.0中java.util.concurrent.Executor的影响。
这一接口为各种线程池服务提供了抽象,它在统一客户视图方面起到了最重要的作用。
无论是Spring 2.0内部实现中,还是各种基于Spring的企业应用,TaskExecutor的应用随处可见,其定义如下。
public interface TaskExecutor {//异步或同步执行用户提交的任务void execute(Runnable task);}开发者可以通过execute(Runnable task)方法将待执行的任务提交给TaskExecutor。
依据不同的TaskExecutor接口实现,这一任务会以异步或同步的方式进行。
如果是同步,则调用者一直处于阻塞状态,直到任务被执行完成。
此时,调用者同目标任务的执行处于同一线程中,因此线程上下文信息能够传播到目标任务的执行过程中。
如果是异步,则一旦提交完任务,调用者即可返回,并继续进行自身的其他操作。
此时,调用者同目标任务的执行位于不同的线程中,因此线程上下文信息很可能不能够在它们之间共享。
应用要合理选择同步或异步。
比如,在调用execute()期间,如果采用Spring受管事务或Acegi提供的企业级安全性服务,则一旦同步或异步选用不当,事务的ACID属性将得不到保证,而且应用的安全性也得不到保障。
Spring 2.0内置的TaskExecutor接口实现见图15-1,图中除了SyncTaskExecutor外,其他实现都是采用异步方式执行提交的任务的。
图15-1 TaskExecutor继承链开发者是否还记得,第5章介绍的SimpleApplicationEventMulticaster,它使用了SyncTaskExecutor辅助完成事件的消费工作,其代码摘录如下。
public class SyncTaskExecutor implements TaskExecutor, Serializable {//同步调用客户提交的任务public void execute(Runnable task) {Assert.notNull(task, "Runnable must not be null");task.run();}}SyncTaskExecutor的使用非常简单,下面给出了示例,摘自Eclipse taskexecutordemo项目。
TaskExecutor te = new SyncTaskExecutor();te.execute(new LogRunner());由于SyncTaskExecutor不存在任何属性,因此它的使用最简单。
在使用这一实现的过程中,并不会处触发新线程的创建工作,因为提交的任务同调用者同处于一个线程中。
相比之下,异步执行用户任务的SimpleAsyncTaskExecutor的使用复杂些,下面给出了示例代码。
此时,它会启动新的Thread,并允许开发者控制并发线程的上限(concurrencyLimit),从而起到一定的资源节流作用。
默认时,concurrencyLimit取值为–1,即不启用资源节流。
<bean id="simpleAsyncTaskExecutor"class="org.springframework.core.task.SimpleAsyncTaskExecutor"><property name="daemon" value="true" /><property name="concurrencyLimit" value="2" /><property name="threadNamePrefix" value="simpleAsyncTaskExecutor" /></bean>在Spring 2.0内部实现中,JMS集成会使用SimpleAsyncTaskExecutor完成JMS消息监听器的注册、执行等工作,我们将在第16章介绍这方面的内容。
每次用户提交新的任务给SimpleAsyncTaskExecutor时,它都会启动新的线程来响应客户请求。
这意味着,它并没有提供线程池功能。
更何况,在Java EE环境中,随便启动新的线程并不是推荐的做法,因为Java EE容器没有办法管理到这些线程。
所以,我们要改进SimpleAsyncTaskExecutor。
Java SE 5.0引入了ThreadPoolExecutor、ScheduledThreadPoolExecutor。
Spring 2.0借助于ConcurrentTaskExecutor和ThreadPoolTaskExecutor能够通过IoC配置形式自定义它们暴露的各个属性。
比如,下面给出ThreadPoolTaskExecutor的使用示例,其暴露的各个属性其实是ThreadPoolExecutor的属性,这体现了DI容器的优势。
<bean id="threadPoolTaskExecutor"class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor"><property name="corePoolSize" value="2" /><property name="keepAliveSeconds" value="200" /><property name="maxPoolSize" value="10" /><property name="queueCapacity" value="60" /></bean>Spring 2.0在集成java.util.Timer任务调度机制过程中,还引入了TimerTaskExecutor。
此时,用户提交的所有任务将交由Timer本身的单个线程执行,其示例配置如下。
这一单个线程会以串行的方式完成用户提交给它的所有任务。
<bean id="timerTaskExecutor"class="org.springframework.scheduling.timer.TimerTaskExecutor"><property name="delay" value="10000" /></bean>在集成Quartz任务调度框架过程中,Spring引入了SimpleThreadPoolTaskExecutor。
此时,用户提交的所有任务将交由Quartz维护的线程池执行,其示例配置如下。
很显然,开发者能够控制这一线程池的具体细节。
在使用Quartz实现任务调度过程中,它会使用内置的线程池处理任务。
<bean id="simpleThreadPoolTaskExecutor"class="org.springframework.scheduling.quartz.SimpleThreadPoolTaskExecutor"><property name="makeThreadsDaemons" value="true"/><property name="threadCount" value="5" /><property name="threadNamePrefix" value="simpleThreadPoolTaskExecutor"/><property name="waitForJobsToCompleteOnShutdown" value="true" /></bean>如果开发者在使用BEA WebLogic 9+和IBM WebSphere 6+版本的Java EE应用服务器,则应用通过JNDI树还能够获得CommonJ提供的Java EE容器受管连接池。