什么是线程池
- 线程池可以简单理解为一组线程的集合, 由线程池来管理各个线程, 我们可以向线程池提交任务, 并由其管理的线程去执行
- 像线程池、连接池这些池化技术, 主要通过对线程/连接进行管理, 使得线程/连接可以复用, 因为线程/连接的创建是很昂贵的(开销大), 所以通过池化技术来进行管理, 避免频繁创建销毁很有必要.
- 创建一个线程池需要哪些组件?
- 首先最基本的是要有线程
- 其次, 我们需要像线程池提交任务, 所以需要有一个存放任务的容器, 比如队列
- 有了这两个部分后, 线程就可以不断的去队列中取任务来执行
- 这只是一个简单的模型, 但是实际中需要考虑比较多的问题:
- 比如多个线程同时去队列取任务会不会有线程安全问题(所以这里可以用阻塞队列);
- 当任务比较少时, 空闲的线程如何处理, 一直空闲着会占用过多资源
- 任务队列的大小如何控制? 任务队列满了怎么办?
- 这些问题都会在后面一一解答
线程池构成
- 先来认识一下Java线程池的基本流程, 网上找了一张图如下
- 运行流程如下
- 首先主线程调用线程池execute方法提交任务
- 线程池首先判断目前已有的线程数量(workerCount)是否小于核心线程是(corePoolSize)
- 如果workerCount<corePoolSize, 则直接创建一个新线程来执行任务, 返回
- 如果workerCOunt>=corePoolSize, 则尝试把任务提交到任务队列
- 如果任务队列未满, 则提交到任务队列, 返回
- 如果任务队列已满, 则判断目前已有线程数(workerCount)是否小于线程池的最大线程数(maxPoolSize), 注意走到这一步的workerCount>=corePoolSize
- 如果workerCount<maxPoolSize, 那么创建一个新线程执行任务, 返回
- 否则, 把任务交由拒绝策略来处理
- 核心线程和非核心线程:
- Java线程池中把线程分为两类, 核心线程和非核心线程
- 核心线程数<=总线程数, 核心线程+非核心线程=总线程数,
- 创建线程时, 先创建的是核心线程, 当到达核心线程数后, 创建的就是非核心线程;
- 任务队列: 这个Java是采用BlockingQueue的实现类, 因此可以用LinkedBlockingQueue、ArrayBlockingQueue等, 可按需选择
- 拒绝策略: 当已有线程数目>=最大线程数, 且任务队列已满时, 会把任务交给拒绝策略来操作; Java线程池提供了四种拒绝策略
- AbortPolicy: 直接丢弃任务, 并抛出异常
- DiscardPolicy: 直接丢弃任务, 不会抛出异常
- CallerRunsPolicy: 让提交该任务的线程来执行, 这样可能会影响任务提交速度
- DicardOldestPolicy: 丢弃掉任务队列中的最老任务(即队头任务), 然后将该任务添加到队列
- 一些核心参数:
- corePoolSize: 线程池的核心线程数
- maxPoolSize: 线程池的最大线程数, corePoolSize <= maxPoolSize
- keepAliveTime: 非核心线程空闲时间达到keepAliveTime时, 会被销毁
- allowCoreThreadTimeOut: 默认核心线程创建出来后是不会被销毁的, 如果该参数为true, 则核心线程空闲超过keepAliveTime后也会被销毁
- defaultHandler: 线程池的拒绝策略
- workQueue: 线程池的任务队列
Java中的线程池
-
Java中的线程池接口是Executor(不要和Executors混淆, Executors是线程池的一个工具类); 继承关系图如下:
-
顶层接口Executor: 其代码也比较简单, 只有一个execute方法用于提交任务
public interface Executor { void execute(Runnable command); }
-
ExecutorService接口: 定义了线程池的关闭(shutdown)方法, 提交任务submit方法, 以及批量任务执行方法; 具体如下
public interface ExecutorService extends Executor { //和线程池关闭相关的系列方法 void shutdown(); List<Runnable> shutdownNow(); boolean isShutdown(); boolean isTerminated(); boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException; //submit方法, 和Executor#execute不同, submit有返回值Future, 可以用来查看任务执行情况 <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; ...... }
-
AbstractExecutorService: 这个抽象类主要实现了submit()和invokeAll这两类方法; 下面摘一些主要方法实现看看
public abstract class AbstractExecutorService implements ExecutorService { //将Runnable任务封装为一个RunnableFuture对象, 这个对象主要是提供给调用者, //可以观察任务的执行完了与否或者成功与否, 以及可以拿到返回值等 protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) { return new FutureTask<T>(runnable, value); } //提交任务方法 public Future<?> submit(Runnable task) { if (task == null) throw new NullPointerException(); //先把Runnable任务封装为RunnableFuture对象 RunnableFuture<Void> ftask = newTaskFor(task, null); //提交执行 execute(ftask); //把RunnableFuture对象返回, 这样调用者就可以通过该对象的方法查看任务执行情况 return ftask; } //....其他内容省略
-
ThreadPoolExecutor: 这是线程池的默认实现类, 该类比较复杂, 我们分步来看
- 内部类: 如下图, 这几个是ThreadPoolExecutor的内部类, 主要分两部分
- Worker: 这就是线程池中的线程实例, 每一个Worker可以理解为一个线程
- 其余四个内部类都是线程池的拒绝策略, 和前面讲的一样
- 成员方法: 这里方法就先不分析了, 比较多和杂, 后面再做分析
- 成员变量: 主要成员变量和前面将的线程池核心参数差不多, 这里挑部分重要的成员变量
//这个ctl通过位运算记录了当前线程数以及线程池的状态两部分信息, 可暂时不看 private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); //接下来这几个都是和线程池的状态有关的, 都是采用位运算方式来获取, 可暂时不看 private static final int COUNT_BITS = Integer.SIZE - 3; private static final int CAPACITY = (1 << COUNT_BITS) - 1; private static final int RUNNING = -1 << COUNT_BITS; private static final int SHUTDOWN = 0 << COUNT_BITS; private static final int STOP = 1 << COUNT_BITS; private static final int TIDYING = 2 << COUNT_BITS; private static final int TERMINATED = 3 << COUNT_BITS; //这个set用来保存创建的每一个worker, 可以理解为线程 private final HashSet<Worker> workers = new HashSet<Worker>(); //一个锁, 主要是当创建worker并添加到set时, 为保证线程安全, 必须拿到锁才能添加 private final ReentrantLock mainLock = new ReentrantLock(); //记录已完成的任务数 private long completedTaskCount; //线程工厂, 工厂模式, 用于创建worker时创建线程 private volatile ThreadFactory threadFactory; //这部分和前面将的核心参数是一个意思, 不在赘述 private final BlockingQueue<Runnable> workQueue; private int largestPoolSize; private volatile RejectedExecutionHandler handler; private volatile long keepAliveTime; private volatile boolean allowCoreThreadTimeOut; private volatile int corePoolSize;
- 内部类: 如下图, 这几个是ThreadPoolExecutor的内部类, 主要分两部分
小结
- 到这里, 我们就基本明白了Java线程池中提交任务的基本流程
- 设计线程池的一些重要部分, 如任务队列, 线程, 一些核心参数都在上面分析ThreadPoolExecutor中有相应的源码
- 了解了这些后, 后面我们再分析提交任务的具体代码分析