多线程与高并发(七)--线程池
Executor
- 提供了void execute(Runnable command);方法。执行command任务,将任务的定义以及运行分离。
ExecutorService
- 继承了Executor,完善了任务执行器的生命周期。
- 提供了submit方法(异步执行任务),将任务完成的返回值封装到Future中。
Callable
- 和Runnable类似,提供了call方法。与Runnable的run方法不同的是,call方法有返回值。
Future
- 代表任务执行的结果,将任务完成的返回值封装到future。
- 通过get()方法获取任务返回值。
- get是阻塞方法,知道可以获取结果才会往下执行。
FutureTask
- 既是一个Future也是一个Callable(实现了RunnableFuture,RunnableFuture继承了Runnable以及Future)。
CompletableFuture
- 提供了对单个以及批量任务的管理方案。
ThreadPoolExecutor
- 继承自AbstractExecutorService(AbstractExecutorService实现了ExecutorService,ExecutorService继承了Executor)。
- 内部维护了两个集合,一个是线程集合,一个是任务集合。
- 七个参数简介
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runn able> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler);
-
- corePoolSize:核心线程数,核心线程是即使空闲时间超过了生存时间也不会被回收。
- maximumPoolsize:最大线程数,最多有多少个线程。
- keepAliveTime:生存时间,线程超过多久没有执行任务会被回收,数量等于核心线程数时不再回收线程。
- unit:生存时间的单位
- workQueue:任务队列,BlockingQueue(使用不同的BlockingQueue会产生不同的线程池)。
- threadFactory:线程工厂,需要实现ThreadFactory,可以自定义实现。
- handler:拒绝策略,线程池所有线程都在忙碌状态,而且任务队列已满,需要执行拒绝策略。拒绝策略可以自定义,默认提供了四种(一般情况都是自定义策略)。
- Abort:抛异常。
- Discard:扔掉,不抛异常。
- DiscardOldest:扔掉排队时间最久的。
- CallerRuns:调用者处理任务。
- 自定义的通常是记录日志并且记录到MySQL、Redis或者kafka中。
- 源码解析:
- 常量
// ctl 可以看做一个int类型的数字,高3位表示线程池状态,低29位表示worker数量 private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); // COUNT_BITS,Integer.size=32,所以COUNT_BITS=29 private static final int COUNT_BITS = Integer.SIZE - 3; // CAPACITY,线程池允许的最大线程数。1左移29位,然后减1,即为2^29-1 private static final int CAPACITY = (1 << COUNT_BITS) - 1; // runState is stored in the high-order bits // 线程池有5中状态,按大小顺序如下:RUNNING<SHUTDOWN<STOP<TIDYING<TERMINATED 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;
- executor方法
public void execute(Runnable command) { if (command == null) throw new NullPointerException(); /* * Proceed in 3 steps: * * 1. If fewer than corePoolSize threads are running, try to * start a new thread with the given command as its first * task. The call to addWorker atomically checks runState and * workerCount, and so prevents false alarms that would add * threads when it shouldn't, by returning false. * * 2. If a task can be successfully queued, then we still need * to double-check whether we should have added a thread * (because existing ones died since last checking) or that * the pool shut down since entry into this method. So we * recheck state and if necessary roll back the enqueuing if * stopped, or start a new thread if there are none. * * 3. If we cannot queue task, then we try to add a new * thread. If it fails, we know we are shut down or saturated * and so reject the task. */ int c = ctl.get(); // worker数量比核心线程数小,直接创建worker执行任务 if (workerCountOf(c) < corePoolSize) { if (addWorker(command, true)) return; // 多线程场景下,值有可能会被别的线程修改,所以要再取一次 c = ctl.get(); } // worker数量超过核心线程数,任务直接进入队列 if (isRunning(c) && workQueue.offer(command)) { int recheck = ctl.get(); // 线程池状态不是RUNNING状态,说明执行过shutdown命令,需要对新加入的任务执行reject()操作。 // 这为什么需要recheck,是因为任务入队列前后,线程池的状态可能会发生变化。 if (! isRunning(recheck) && remove(command)) reject(command); // 这为什么需要判断0值,主要是在线程池构造方法中,核心线程数允许为0 else if (workerCountOf(recheck) == 0) addWorker(null, false); } /** * 如果线程池不是运行状态,或者任务进入队列失败,则尝试创建worker执行任务。 * 这有三点需要注意: * 1.线程池不是运行状态时,addWorker内部会判断线程池状态 * 2.addWorker第2个参数表示是否创建核心线程 * 3.addWorker返回false,则说明任务执行失败,需要执行reject操作 */ else if (!addWorker(command, false)) reject(command); }
- addWorker方法
/** * Checks if a new worker can be added with respect to current * pool state and the given bound (either core or maximum). If so, * the worker count is adjusted accordingly, and, if possible, a * new worker is created and started, running firstTask as its * first task. This method returns false if the pool is stopped or * eligible to shut down. It also returns false if the thread * factory fails to create a thread when asked. If the thread * creation fails, either due to the thread factory returning * null, or due to an exception (typically OutOfMemoryError in * Thread.start()), we roll back cleanly. * * @param firstTask the task the new thread should run first (or * null if none). Workers are created with an initial first task * (in method execute()) to bypass queuing when there are fewer * than corePoolSize threads (in which case we always start one), * or when the queue is full (in which case we must bypass queue). * Initially idle threads are usually created via * prestartCoreThread or to replace other dying workers. * * @param core if true use corePoolSize as bound, else * maximumPoolSize. (A boolean indicator is used here rather than a * value to ensure reads of fresh values after checking other pool * state). * @return true if successful */ private boolean addWorker(Runnable firstTask, boolean core) { retry: // 外层自旋 for (;;) { int c = ctl.get(); int rs = runStateOf(c); /** * (rs > SHUTDOWN) || * (rs == SHUTDOWN && firstTask != null) || * (rs == SHUTDOWN && workQueue.isEmpty()) * 1.线程池状态大于SHUTDOWN时,直接返回false * 2.线程池状态等于SHUTDOWN,且firstTask不为null,直接返回false * 3.线程池状态等于SHUTDOWN,且队列为空,直接返回false */ // Check if queue empty only if necessary. if (rs >= SHUTDOWN && ! (rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty())) return false; // 内层自旋 for (;;) { int wc = workerCountOf(c); // worker数量超过容量,直接返回false if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize)) return false; // 使用CAS的方式增加worker数量。 // 若增加成功,则直接跳出外层循环进入到第二部分 if (compareAndIncrementWorkerCount(c)) break retry; c = ctl.get(); // Re-read ctl // 线程池状态发生变化,对外层循环进行自旋 if (runStateOf(c) != rs) continue retry; // 其他情况,直接内层循环进行自旋即可 // else CAS failed due to workerCount change; retry inner loop } } boolean workerStarted = false; boolean workerAdded = false; Worker w = null; try { w = new Worker(firstTask); final Thread t = w.thread; if (t != null) { final ReentrantLock mainLock = this.mainLock; // worker的添加必须是串行的,因此需要加锁 mainLock.lock(); try { // Recheck while holding lock. // Back out on ThreadFactory failure or if // shut down before lock acquired. // 这里需要重新检查线程池状态 int rs = runStateOf(ctl.get()); if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)) { // worker已经调用过了start()方法,则不在创建worker if (t.isAlive()) // precheck that t is startable throw new IllegalThreadStateException(); // worker创建并添加到workers成功 workers.add(w); // 国内新年largestPoolSize变量 int s = workers.size(); if (s > largestPoolSize) largestPoolSize = s; workerAdded = true; } } finally { mainLock.unlock(); } // 启动worker线程 if (workerAdded) { t.start(); workerStarted = true; } } } finally { // worker线程启动失败,说明线程池状态发生了变化(关闭操作被执行),需要进行shutdown相关操作 if (! workerStarted) addWorkerFailed(w); } return workerStarted; }
- 线程池worker任务单元:Worker类继承了AbstractQueueSynchronizer(AQS),同时实现了Runnable接口。是一个任务的同时是一把锁。
- 常量
ForkJoinPool
- 分解汇总的任务。
- 用很少的线程可以执行很多的任务(子任务)TPE做不到先执行子任务。
- CPU密集型
Executors
线程工具类,可以看做是线程池的工厂。
- 线程池数量的选择
- 如果线程池中线程的数量过多,最终它们会竞争稀缺的处理器和内存资源,浪费大量的时间在上下文切换。反之,如果线程的数目过少,处理器的一些和可能就无法充分使用。通过压测来进行判断。
- SingleThreadExecutor
- 线程池中只有一个线程。
- 为什么要有单线程的线程池?
- 有任务队列,可以管理线程的生命周期。
- CachedThreadPool
- 核心线程数为0,最大线程数时Integer.MAX_VALUE,生存时间为60s,任务队列是SynchronousQueue(来一个任务就会被线程执行)。
- FixedThreadPool
- 核心线程=最大线程(固定数量线程池)。
- Cached VS Fixed
- 如果任务请求不平均,使用Cached。
- 如果任务请求比较平稳,使用Fixed。
- ScheduledThreadPool
- 专门用来执行定时任务的线程池。
- 指定核心线程数,最大线程数是整数最大值,任务队列是DelayedWorkQueue(可以指定经过多长时间之后运行)。
- 经常使用的定时器框架:quarts。
- WorkStealingPool(本质是一个ForkJoinPool)
- 和以上线程池不同的是,WorkStealingPool线程池的每一个线程都有自己对应的任务队列。当某个线程执行完自己的任务之后会去别的线程的任务队列取任务。
- 往任务队列放任务以及消费自己任务队列的任务时不需要加锁,取别的线程的任务队列的任务时需要加锁。
- Executors中有WorkStealingPool是为了提供一个方便使用的接口
- List.parallelStream:并行流
- 面试题:假如提供一个闹钟服务,订阅这个服务的人特别多,大概有10亿左右,如何优化?
- 将任务分发到边缘的服务器上,在边缘服务器通过多线程去消费处理。
- 并行和并发的区别:
- 如果某个系统支持两个或者多个动作(Action)同时存在,那么这个系统就是一个并发系统。如果某个系统支持两个或者多个动作同时执行,那么这个系统就是一个并行系统。并发系统与并行系统这两个定义之间的关键差异在于“存在”这个词。 --- 《并发的艺术》 — 〔美〕布雷谢斯

浙公网安备 33010602011771号