首先介绍线程池之前,我们应该了解线程池是个是个什么东西?顾名思义,线程池就是一个存放线程的容器,当我们有一个程序需要执行很多任务,每次执行任务都会创建一个线程,等待任务执行结束,就销毁线程。看上去这样的操作并没有什么问题,实际上创建线程和销毁线程都会很大的占用计算机资源。如果有一样东西能将我们使用过的线程不销毁,而是可以重复使用,也就是说,线程完成执行任务之后可以继续执行别的任务,这样就完美的解决的资源消耗的问题,这就是线程池的作用。
讲完什么是线程池,接下来我们了解一下Java中是如果创建线程池的。
ThreadPoolExecutor
public class ThreadPoolExecutor extends AbstractExecutorService {
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;
}
}
这个类就是我们需要了解的重中之重啦,这是创建线程的类ThreadPoolExecutor,看名字也能翻译成线程池。
这个类一共有4个构造器,但是我们仔细观察就知道了,三个构造器都是调用最后一个构造器的。所以我们就讲解一个最后一个构造器的参数意义
int corePoolSize:核心线程数,也就是线程中的执行线程数
int maximumPoolSize:最大线程数,如果线程数达到核心线程数时,并且任务队列已经满了,则开启新的线程执行任务,但是线程池中的总线程数不能超过此参数
long keepAliveTime:生存时间,线程数超过了核心线程数,当有线程空闲下来时,并且这个线程空闲的时间达到了这个生存时间值就会被销毁,直到线程数小于等于核心线程数为止。
TimeUnit unit:生存时间的单位
TimeUnit.DAYS;//天
TimeUnit.HOURS;//小时
TimeUnit.MINUTES;//分钟
TimeUnit.SECONDS;//秒
TimeUnit.MILLISECONDS;//毫秒
TimeUnit.MICROSECONDS;//微秒
TimeUnit.NANOSECONDS;//纳秒
BlockingQueue<Runnable> workQueue:阻塞任务队列,当线程池中的执行任务线程达到了核心线程数时,就会把以后新的任务加入队列中,等待空闲线程来执行。队列一共如下有4种,都是继承此队列接口
ArrayBlockingQueue<E>类:这是一个有界队列,采用FIFO形式,当任务太多队列已满时,将会采取拒绝策略,下文会讲到。
LinkedBlockingDeque<E>类:这是一个无界队列,采用FIFO形式,无法现在立即执行的任务都会报存到这个队列中,因此当任务堆积过多时会严重影响性能。
PriorityBlockingQueue<E>类:这是一个优先级的队列。
SynchronousQueue<E>类:只有当前一个任务完成之后才会向队列中加入新的任务
ThreadFactory threadFactory:线程工厂,用于创建线程线程池
RejectedExecutionHandler handler:拒绝策略,当任务队列满了,并且线程数达到了最大值,就会采取拒绝策略
提供了4种策略:
AbortPolicy:直接抛出异常,默认策略;
CallerRunsPolicy:用调用者所在的线程来执行任务;
DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;
DiscardPolicy:直接丢弃任务;
也可以自定义拒绝策略,实现RejectedExecutionHandler接口,重写rejectedExecution(Runnable r, ThreadPoolExecutor executor)方法。
execute(Runnable command)方法
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false))
reject(command);
}
这段源码在JDK1.8中ThreadPoolExecutor类中,表示执行这个线程任务时的主要逻辑:
1、判断command是否为null,是则抛出异常,否则进入2
2、判断工作线程是否小于核心线程,是则加入工作集,并执行,否则进入3
3、判断线程是为运行状态,并将此任务加入阻塞队列中,成功则进入4,否则进入5
4、再次检测线程池是否为运行态,因为加入队列时有可能其他线程调用了shoudown()或者shutdownNow()方法,将使线程池停止运行,如果线程池不为运行态,并且移除任务成功,则进入7,否则进入5
5、检测工作集工作线程数是否为0,是就新建一个线程,并且不是以核心线程数为参数,传入false
6、检测判断加入工作集失败,则进入7
7、进入拒绝策略
以上就是线程池的主要执行逻辑代码了。
Java中有几种为我们设定好的线程池类型,都是基于以上的线程池实现的,使用Executors类调用:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
newFixedThreadPool:由参数可知核心线程数与最大线程数是一样的,这说明了当任务数达到核心线程数时,只能往队列里塞了,当任务堆积过多时,阻塞队列将会影响性能 newSingleThreadExecutor:核心线程数与最大线程数都是1,这使得所有的任务都是顺序执行的 newCachedThreadPool:核心线程数为0,最大线程数为最大值,使用了同步队列,这种队列的特性是一个线程向队列中放元素的时候必须同时另一个线程从队列中取出元素,否则是放不进去的,60s的生存时间, 原理为: 当第1个任务提交过来,线程池中的线程数为0,于是,新建一个线程,并执行任务,此时,线程池中有1个线程; 当第2个任务提交过来,由于无法放到队列中,于是再创建一个线程,此时线程池中有2个线程; 当第3个任务提交过来,若前两个线程有一个处理完了,这个时候由于是循环从队列中取,所以可以放到工作队列中 当任务处理完成后,如果线程的空闲时间达到了60s,则会销毁线程,最终线程池中的线程数为0