1 概述
创建一个线程非常简便,但是如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。
为了解决这样的问题,Java提出了线程池这个解决方法.所谓的线程池其实就是一个存放线程的容器.调用线程池去执行并发任务时,从线程池中取出线程去执行任务,每个线程 执行完任务,并不被销毁,而是被线程池回收,下一次继续执行任务.
2 线程池的种类
2.1 JDK中的线程池体系
java.uitl.concurrent包是Java线程池的核心包.里面包含众多与线程池相关的接口和类.
2.1.1 Executor
Executor是一个顶层接口,在它里面只声明了一个方法execute(Runnable),返回值为void,参数为Runnable类型,从字面意思可以理解,就是用来执行传进去的任务的.
2.1.2 ExecutorService
ExecutorService接口继承了Executor接口,并声明了一些方法:submit、invokeAll、invokeAny以及shutDown等方法.
2.1.3 AbstractExecutorService
抽象类AbstractExecutorService实现了ExecutorService接口,基本实现了ExecutorService中声明的所有方法;
2.1.4 ThreadPoolExecutor
ThreadPoolExecutor继承了类AbstractExecutorService。
2.2 ThreadPoolExecutor类
ThreadPoolExecutor类是Java自带的一个线程池核心类,创建一个 ThreadPoolExecutor对象就是创建了一个线程池.
ThreadPoolExecutor类中提供了四个构造方法:
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)
...
}
从上面的代码可以得知,ThreadPoolExecutor继承了AbstractExecutorService类,并提供了四个构造器,事实上,通过观察每个构造器的源码具体实现,发现前面三个构造器都是调用的第四个构造器进行的初始化工作。
2.2.1构造方法参数
2.2.1.1 corePoolSize
核心池的大小,这个参数跟后面讲述的线程池的实现原理有非常大的关系。在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法,从这2个方法的名字就可以看出,是预创建线程的意思,即在没有任务到来之前就创建corePoolSize个线程或者一个线程。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中;
2.2.1.2 maximumPoolSize
线程池最大线程数,这个参数也是一个非常重要的参数,它表示在线程池中最多能创建多少个线程;
2.2.1.3 keepAliveTime
表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0;
2.2.1.4 unit
参数keepAliveTime的时间单位,有7种取值,
在TimeUnit类中有7种静态属性:
TimeUnit.DAYS; //天
TimeUnit.HOURS; //小时
TimeUnit.MINUTES; //分钟
TimeUnit.SECONDS; //秒
TimeUnit.MILLISECONDS; //毫秒
TimeUnit.MICROSECONDS; //微妙
TimeUnit.NANOSECONDS; //纳秒
2.2.1.5 workQueue
一个阻塞队列,用来存储等待执行的任务,这个参数的选择也很重要,会对线程池的运行过程产生重大影响,一般来说,这里的阻塞队列有以下几种选择:
ArrayBlockingQueue
LinkedBlockingQueue
SynchronousQueue
ArrayBlockingQueue和PriorityBlockingQueue使用较少,一般使用LinkedBlockingQueue和Synchronous。线程池的排队策略与BlockingQueue有关。
2.2.1.6 threadFactory
线程工厂,主要用来创建线程;可以使用如下线程工厂:
org.springframework.scheduling.concurrent.CustomizableThreadFactory
org.springframework.scheduling.concurrent.ThreadPoolExecutorFactoryBean
com.google.common.util.concurrent.ThreadFactoryBuilder 这个需要调用build()方法创建线程工厂:
ThreadFactory factory = new ThreadFactoryBuilder()
.setNameFormat(“mlsama”) .build();
2.2.1.7 handler
表示当拒绝处理任务时的策略,有以下四种取值:
ThreadPoolExecutor.AbortPolicy()
丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.DiscardPolicy()
也是丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy()
丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy()
由调用线程处理该任务
2.2.2 方法
execute()方法实际上是Executor中声明的方法,在ThreadPoolExecutor进行了具体的实现,这个方法是ThreadPoolExecutor的核心方法,通过这个方法可以向线程池提交一个任务,交由线程池去执行。
submit()方法是在ExecutorService中声明的方法,在AbstractExecutorService就已经有了具体的实现,在ThreadPoolExecutor中并没有对其进行重写,这个方法也是用来向线程池提交任务的,但是它和execute()方法不同,它能够返回任务执行的结果,去看submit()方法的实现,会发现它实际上还是调用的execute()方法,只不过它利用了Future来获取任务执行结果。
shutdown()和shutdownNow()是用来关闭线程池的。
还有很多其他的方法:
比如:getQueue() 、getPoolSize() 、getActiveCount()、getCompletedTaskCount()等获取与线程池相关属性的方法
2.2.3 ThreadPoolExecutor 原理
2.2.3.1 线程池状态
ThreadPoolExecutor中定义了一个volatile变量,另外定义了几个static final变量表示线程池的各个状态:
volatile int runState;
static final int RUNNING = 0;
static final int SHUTDOWN = 1;
static final int STOP = 2;
static final int TERMINATED = 3;
runState表示当前线程池的状态,它是一个volatile变量用来保证线程之间的可见性;
下面的几个static final变量表示runState可能的几个取值。
当创建线程池后,初始时,线程池处于RUNNING状态;
如果调用了shutdown()方法,则线程池处于SHUTDOWN状态,此时线程池不能够接受新的任务,它会等待所有任务执行完毕;
如果调用了shutdownNow()方法,则线程池处于STOP状态,此时线程池不能接受新的任务,并且会去尝试终止正在执行的任务;
当线程池处于SHUTDOWN或STOP状态,并且所有工作线程已经销毁,任务缓存队列已经清空或执行结束后,线程池被设置为TERMINATED状态。
2.2.3.2 任务的执行
在了解将任务提交给线程池到任务执行完毕整个过程之前,我们先来看一下ThreadPoolExecutor类中其他的一些比较重要成员变量:
//任务缓存队列,用来存放等待执行的任务
private final BlockingQueue<Runnable> workQueue;
//线程池主要状态锁,对线程池状态(比如线程池大小、runState等的改变都要使用这个锁)
private
final
ReentrantLock mainLock = new
ReentrantLock();
//用来存放工作集
private final HashSet<Worker> workers = new HashSet<Worker>();
//线程存活时间
private volatile long keepAliveTime;
//是否允许为核心线程设置存活时间
private volatile boolean allowCoreThreadTimeOut;
//核心池大小即线程池中的线程数目大于这个参数时,提交的任务会被放进任务缓存队列)
private volatile int corePoolSize;
//线程池最大能容忍的线程数
private volatile int maximumPoolSize;
private volatile int poolSize; //线程池中当前的线程数
private volatile RejectedExecutionHandler handler; //任务拒绝策略
private volatile ThreadFactory threadFactory; //线程工厂,用来创建线程
private int largestPoolSize; //用来记录线程池中曾经出现过的最大线程数
private long completedTaskCount; //用来记录已经执行完毕的任务个数
为了方便理解,举个简单的例子:
有一个工厂,工厂里面有10个工人(corePoolSize),每个工人同时只能做一件任务。因此只要当10个工人中有工人是空闲的,来了任务就分配给空闲的工人做;当10个工人都有任务在做时,如果还来了任务,就把任务进行排队(workQueue)等待;
如果说新任务数目增长的速度远远大于工人做任务的速度,那么此时工厂主管可能会想补救措施,比如重新招4个临时工人进来;然后就将任务也分配给这4个临时工人做;如果说着14个工人做任务的速度还是不够,此时工厂主管可能就要考虑不再接收新的任务或者抛弃前面的一些任务了。
当这14个工人当中有人空闲时,而新任务增长的速度又比较缓慢,工厂主管可能就考虑辞掉4个临时工了,只保持原来的10个工人,毕竟请额外的工人是要花钱的。这个例子中的corePoolSize就是10,而maximumPoolSize就是14(10+4)。也就是说corePoolSize就是线程池大小,maximumPoolSize在我看来是线程池的一种补救措施,即任务量突然过大时的一种补救措施。不过为了方便理解,在本文后面还是将corePoolSize翻译成核心池大小。largestPoolSize只是一个用来起记录作用的变量,用来记录线程池中曾经有过的最大线程数目,跟线程池的容量没有任何关系。
2.2.3.3 线程池中的线程初始化
默认情况下,创建线程池之后,线程池中是没有线程的,需要提交任务之后才会创建线程. 在实际中如果需要线程池创建之后立即创建线程,可以通过以下两个方法办到:
prestartCoreThread():初始化一个核心线程;
prestartAllCoreThreads():初始化所有核心线程
下面是这2个方法的实现:
public boolean prestartCoreThread() {
return addIfUnderCorePoolSize(null); //注意传进去的参数是null
}
public int prestartAllCoreThreads() {
int n = 0;
while (addIfUnderCorePoolSize(null))//注意传进去的参数是null
++n;
return n;
}
传进去的参数为null,则最后执行线程会阻塞在getTask方法中的r = workQueue.take();即等待任务队列中有任务才会创建线程。
2.2.3.4 任务缓存队列及排队策略
任务缓存队列,即workQueue,它用来存放等待执行的任务。workQueue的类型为BlockingQueue<Runnable>,通常可以取下面三种类型:
1.ArrayBlockingQueue:基于数组的先进先出队列,此队列创建时必须指定大小;
2.LinkedBlockingQueue:基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为Integer.MAX_VALUE;
3.synchronousQueue:这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务。
2.2.3.5 任务拒绝策略
当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略,通常有以下四种策略:
1.ThreadPoolExecutor.AbortPolicy():丢弃任务并抛出RejectedExecutionException异常。(默认)
2.ThreadPoolExecutor.DiscardPolicy():也是丢弃任务,但是不抛出异常。
3.ThreadPoolExecutor.DiscardOldestPolicy():丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
4.ThreadPoolExecutor.CallerRunsPolicy():由调用线程处理该任务
为了实现某些特殊的业务需求,用户可以选择使用自定义策略,只需实现RejectedExecutionHandler接口即可.
2.2.3.6 线程池的关闭
ThreadPoolExecutor提供了两个方法,用于线程池的关闭,分别是shutdown()和shutdownNow(),其中:
shutdown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务
shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务.
2.2.3.7线程池容量的动态调整
ThreadPoolExecutor提供了动态调整线程池容量大小的方法:setCorePoolSize()和setMaximumPoolSize(),
setCorePoolSize:设置核心池大小
setMaximumPoolSize:设置线程池最大能创建的线程数目大小
当上述参数从小变大时,ThreadPoolExecutor进行线程赋值,还可能立即创建新的线程来执行任务。
2.2.4 实例
//创建线程池
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
0,100,10L,
TimeUnit.SECONDS, new SynchronousQueue(),
new ThreadPoolExecutorFactoryBean(),
new ThreadPoolExecutor.AbortPolicy());
//执行任务task
threadPool.execute(Runnable task);
2.3 Executors
Executors也是JDK中的一个类, Executors利用工厂模式向我们提供了4种线程池实现方式,但是并不推荐使用,原因是使用Executors创建线程池使用默认参数,而且默认使用的参数会导致资源浪费,不可取。
2.3.1 创建线程池的方法
newFixedThreadPool 和 newSingleThreadExecutor:
主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至 OOM。
newCachedThreadPool 和 newScheduledThreadPool:
主要问题是线程数最大数是 Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至 OOM。
2.3.2 创建线程
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
//执行任务
cachedThreadPool.execute(Runnable task);
其他3个方法类似于此.
2.4 spring的ThreadPoolExecutorFactoryBean类
ThreadPoolExecutorFactoryBean类是spring框架在JDK的基础上封装出来的,在org.springframework.scheduling.concurrent包下.它继承ThreadFactory,内部定义了一个线程工厂用于生产线程,在创建这个类的对象时就可以使用其默认的ThreadFactory.通过这个类,可以很简单创建一个线程池.
2.4.1 xml配置
<!--线程池配置-->
<bean id="threadFactory" class="org.springframework.scheduling.concurrent.CustomizableThreadFactory"/>
<bean id="threadPoolTaskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
<property name="corePoolSize" value="1"/> <!--核心线程数 -->
<property name="maxPoolSize" value="16"/> <!--最大线程数 -->
<property name="keepAliveSeconds" value ="3000"/> <!--线程最大空闲时间 -->
<property name="queueCapacity" value="200"/> <!-- 队列大小 -->
<!--线程名称前缀,用于查日记-->
<property name="threadNamePrefix" value="mlsama"/>
<!--线程池工厂,不配也可以,有默认的.ThreadPoolTaskExecutor本身就继承了ThreadFactory-->
<property name="threadFactory" ref="threadFactory"/>
<property name="rejectedExecutionHandler"><!--拒绝策略-->
<!--CallerRunsPolicy:由调用线程处理该任务-->
<bean class="java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy"/>
</property>
</bean>
然后导入对象使用即可:
@Resource(name = "threadPoolTaskExecutor")
private ThreadPoolTaskExecutor threadPoolTaskExecutor;
threadPoolTaskExecutor.execute(Runnable task);
2.4.2 代码实现
ThreadPoolTaskExecutor threadPool2 = new ThreadPoolTaskExecutor();
threadPool2.setCorePoolSize(0); //核心线程数
threadPool2.setMaxPoolSize(Integer.parseInt(configs[1]));//最大线程数
threadPool2.setKeepAliveSeconds(3000); //线程最大空闲时间
threadPool2.setQueueCapacity(100); //队列
threadPool2.setThreadNamePrefix("mlsama"); //线程名称前缀
threadPool2.setThreadFactory(new ThreadPoolExecutorFactoryBean());//线程工厂,有默认的
//拒绝策略
threadPool2.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
//初始化线程池,不然报错
threadPool2.initialize();
threadPool2.execute(Runnable task);//执行任务