一、说明
在项目中添加访问日志、保存异常信息等,虽然能够有效地管理系统,但与此同时也带来了系统性能、用户体验差等不好的问题,原因是,我们需要花费相当一部分资源去处理这些数据。如何解决这些问题呢?使用线程池的技术可以很好地解决这个问题。
相对于单线程在创建时需要耗费大量资源、维护困难的问题,spring提供的线程池技术可以帮助我们管理这些线程并避免频繁地去创建和销毁它们,对于系统的性能和稳定性有很好的提升!
二、线程池API说明
ThreadPoolExecutor是Java线程池技术的核心类,它继承了AbstractExecutorService并提供四个构造方法:
public class ThreadPoolExecutor extends AbstractExecutorService {
.....
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue);
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory);
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler);
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);
...
}
前面三个构造器都是调用的第四个构造器进行的初始化工作。构造器中的参数说明如下:
corePoolSize:核心池的大小,即正常情况下可容纳多少个线程同时执行,当线程数量超过corePoolSize时,就会把之后的线程放到队列中。
maximumPoolSize:线程池里最多可容纳线程的数量,默认情况下,现成吃的数量为corePoolSize,但当待执行线程增多时,线程池会临时开辟出额外的地方来创建线程,此时线程池可容纳的数量就是maximumPoolSize。
keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize。
unit:参数keepAliveTime的时间单位,有7种取值,在TimeUnit类中有7种静态属性:
TimeUnit.DAYS; //天
TimeUnit.HOURS; //小时
TimeUnit.MINUTES; //分钟
TimeUnit.SECONDS; //秒
TimeUnit.MILLISECONDS; //毫秒
TimeUnit.MICROSECONDS; //微妙
TimeUnit.NANOSECONDS; //纳秒
workQueue:一个阻塞队列,用来存储等待执行的任务,这个参数的选择也很重要,会对线程池的运行过程产生重大影响,一般来说,这里的阻塞队列有以下几种选择:
ArrayBlockingQueue;
LinkedBlockingQueue;
SynchronousQueue;
threadFactory:线程工厂,主要用来创建线程。
handler:表示当拒绝处理任务时的策略,有以下四种取值:
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
ThreadPoolExecutor继承了AbstractExecutorService,那么AbstractExecutorService里有哪些方法呢?
public abstract class AbstractExecutorService implements ExecutorService {
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) { };
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) { };
public Future<?> submit(Runnable task) {};
public <T> Future<T> submit(Runnable task, T result) { };
public <T> Future<T> submit(Callable<T> task) { };
private <T> T doInvokeAny(Collection<? extends Callable<T>> tasks,
boolean timed, long nanos)
throws InterruptedException, ExecutionException, TimeoutException {
};
public <T> T invokeAny(Collection<? extends Callable<T>> tasks)
throws InterruptedException, ExecutionException {
};
public <T> T invokeAny(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException {
};
public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
throws InterruptedException {
};
public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException {
};
}
从上面的代码我们知道,AbstractExecutorService实现了ExecutorService接口,而在ExecutorService中主要有以下方法:
public interface ExecutorService extends Executor {
void shutdown();
boolean isShutdown();
boolean isTerminated();
boolean awaitTermination(long timeout, TimeUnit unit)
throws InterruptedException;
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
throws InterruptedException;
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException;
<T> T invokeAny(Collection<? extends Callable<T>> tasks)
throws InterruptedException, ExecutionException;
<T> T invokeAny(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
而ExecutorService又是继承了Executor接口,我们看一下Executor接口的实现:
public interface Executor {
void execute(Runnable command);
}
Executor是一个顶层接口,在它里面只声明了一个方法execute(Runnable),返回值为void,参数为Runnable类型,从字面意思可以理解,就是用来执行传进去的任务的;
然后ExecutorService接口继承了Executor接口,并声明了一些方法:submit、invokeAll、invokeAny以及shutDown等;
抽象类AbstractExecutorService实现了ExecutorService接口,基本实现了ExecutorService中声明的所有方法;
然后ThreadPoolExecutor继承了类AbstractExecutorService。
在ThreadPoolExecutor类中有几个非常重要的方法:
execute()
submit()
shutdown()
shutdownNow()
execute():实际上是Executor中声明的方法,在ThreadPoolExecutor进行了具体的实现,这个方法是ThreadPoolExecutor的核心方法,通过这个方法可以向线程池提交一个任务,交由线程池去执行。
submit():在ExecutorService中声明的方法,在AbstractExecutorService就已经有了具体的实现,在ThreadPoolExecutor中并没有对其进行重写,这个方法也是用来向线程池提交任务的,但是它和execute()方法不同,它能够返回任务执行的结果,去看submit()方法的实现,会发现它实际上还是调用的execute()方法,只不过它利用了Future来获取任务执行结果(Future相关内容将在下一篇讲述)。
shutdown()和shutdownNow()是用来关闭线程池的。
以上关于线程池api的介绍来源于这里,如需了解详细,可点击。
三、spring配置线程池
首先创建线程池的配置文件spring-thread.properties,加入线程池的配置参数:
#quantity of core thread
core_pool_size=5
#max quantity of thread
max_pool_size=50
#length of queue
queue_capacity=1000
#alive time
keep_alive_seconds=60
然后在applicationContext.xml中申明线程池的bean:
<!-- spring 线程池的配置 begin -->
<context:property-placeholder location="classpath:spring-thread.properties" ignore-unresolvable="true" />
<bean id="taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
<!-- 核心线程数 -->
<property name="corePoolSize" value="${core_pool_size}" />
<!-- 最大线程数 -->
<property name="maxPoolSize" value="${max_pool_size}" />
<!-- 队列最大长度 -->
<property name="queueCapacity" value="${queue_capacity}" />
<!-- 线程池维护线程所允许的空闲时间,默认为60s -->
<property name="keepAliveSeconds" value="${keep_alive_seconds}" />
</bean>
<!-- 注解式 -->
<task:annotation-driven />
<!-- spring 线程池的配置 end -->
注意:需要配置xmlns和xsi
xmlns:task="http://www.springframework.org/schema/task"
http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd
四、使用线程池技术保存日志信息
首先,引入线程池服务bean:
@Resource(name = "taskExecutor")
private TaskExecutor taskExecutor;
然后,创建保存日志的线程类(内部类):
/**
* 保存日志的线程
*
* @author xuyong
*
*/
private static class saveLog implements Runnable {
private SysLogVo log;
private SysLogService logService;
@SuppressWarnings("unused")
public saveLog() {
super();
}
public saveLog(SysLogVo log, SysLogService logService) {
super();
this.log = log;
this.logService = logService;
}
@Override
public void run() {
logService.save(log);
}
}
最后,在业务中即可调用线程方法:
taskExecutor.execute(new saveLog(sysLogVo,logService));
sysLogVo:日志的实体类对应的VO,logService为引入的日志服务类,完整代码可参考上篇博客: