面试题
平时怎么使用多线程?哪些好处?线程池的几个核心参数的意义?
线程池概念
-
简介
顾名思义,管理线程的池子
-
好处
-
降低线程创建和线程销毁造成的开销。
-
提高响应速度。任务到达时,相对于手工创建一个线程,直接从线程池中拿线程,速度肯定要快很多
-
提高线程可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统稳定性,使用线程池可以统一进行分配、调优和监控
-
线程池创建
-
创建线程池底层均会调用ThreadPoolExecutor构造函数
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) { this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, Executors.defaultThreadFactory(), defaultHandler); }
参数介绍:
-
corePoolSize:核心线程最大数量,通俗点来讲就是,线程池中常驻线程的最大数量。线程池新建线程的时候,如果当前线程总数小于corePoolSize,则新建的是核心线程;如果超过corePoolSize,则新建的是非核心线程。
-
maximumPoolSize:线程池中运行最大线程数(包括核心线程和非核心线程)
-
keepAliveTime:线程池中空闲线程(仅适用于非核心线程)所能存活的最长时间。当需要执行的任务很多,线程池的线程数大于核心池的大小时,keepAliveTime才起作用。
-
unit:存活时间单位,与keepAliveTime搭配使用(TimeUnit.DAYS,TimeUnit.HOURS,TimeUnit.MINUTES,TimeUnit.MILLISECONDS,TimeUnit.MICRODECONDS)
-
workQueue:存放任务的阻塞队列(维护着等待执行的 Runnable对象。当所有的核心线程都在干活时,新添加的任务会被添加到这个队列中等待处理,如果队列满了,则新建非核心线程执行任务)。
-
handler:线程池饱和策略
-
常见典型工作队列
-
ArrayBlockingQueue
使用数组实现的有界阻塞队列,特性是先进先出
-
LinkedBlockingQueue
使用链表实现的阻塞队列,特性是先进先出,可以设置其容量,默认为Integer.MAX_VALUE
-
PriorityBlockingQueue
使用平衡二叉树堆,实现的具有优先级的无界阻塞队列
-
DelayQueue
无界阻塞延迟队列,队列中每个元素均有过期时间,当从队列获取元素时,只有过期元素才会出队列。队列头元素是最快要过期的元素
-
SynchronousQueue
不存储元素的阻塞队列,每个插入操作,必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态
线程池执行流程
-
执行流程图
-
线程池处理新提交任务流程
-
判断线程池中核心线程数是否已达阀值corePoolSize,若否,则创建一个新核心线程执行任务
-
若核心线程数已达阀值corePoolSize,判断阻塞队列workQueue是否已满,若未满,则将新任务添加进阻塞队列
-
若已满,判断线程池中线程数是否达到阀值maximumPoolSize,若否,则新建一个非核心线程执行任务。若达到阀值,则执行线程池饱和策略
-
-
线程池饱和策略
-
AbortPolicy:直接抛出一个异常阻止系统正常运行,默认策略
-
DiscardPolicy:直接丢弃任务,不予任何处理也不抛出异常(如果允许任务丢失,建议此方案)
-
DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加入队列中尝试再次提交当前任务
-
CallerRunsPolicy:"调用者运行"一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者,从而降低新任务的流量(主线程中执行任务)
-
-
结构角度执行流程图
常见典型线程池
SingleThreadExecutor(单线程池)
-
源码
public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); }
-
源码分析
SingleThreadExecutor的corePoolSize和maximumPoolSize被设置为1,使用无界队列LinkedBlockingQueue作为线程池的工作队列
-
适用场景
适用于需要保证顺序地执行各个任务,并且在任意时间点,不会有多个线程是活动的应用场景。适用串行执行场景
-
执行流程图
-
当线程池中没有线程时,会创建一个新的线程来执行任务
-
当前线程池中有一个线程后,将新任务加入LinkedBlockingQueue
-
线程执行完第一个任务后,会在一个无限循环中反复从LinkedBlockingQueue获取任务来执行
-
FixedThreadPool(固定大小线程池)
-
源码
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), threadFactory); }
-
源码分析
corePoolSize等于maximumPoolSize,所以线程池中只有核心线程,使用无界队列LinkedBlockingQueue作为线程池的工作队列。
FixedThreadPool是一种线程数量固定的线程池,当线程处于空闲状态时,他们并不会被回收,除非线程池被关闭。当所有线程都处于活动状态时,新的任务都会处于等待状态,直到有线程空闲出来。
-
适用场景
适用于处理CPU密集型的任务,确保CPU在长期被工作线程使用的情况下,尽可能的减少分配线程,适用执行长期的任务
-
执行流程图
-
如果当前运行的线程数少于corePoolSize,则创建新线程来执行任务
-
在线程达到corePoolSize后,将新任务放到LinkedBlockingQueue阻塞队列中
-
线程执行完(1)中的任务后,会在循环中反复从LinkedBlockingQueue获取任务来执行
-
CachedThreadPool(可缓存线程池)
-
源码
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }
-
源码分析
核心线程数为0,总线程数量阀值为Integer.MAX_VALUE,即可以创建无限的非核心线程,使用SynchronousQueue不存储元素的阻塞队列
-
使用场景
执行大量短生命周期任务。因为maximumPoolSize是无界的,所以提交任务的速度 > 线程池中线程处理任务的速度就要不断创建新线程。每次提交任务,都会立即有线程去处理,因此CachedThreadPool适用于处理大量、耗时少的任务
-
执行流程图
-
先执行SynchronousQueue的offer方法提交任务,并查询线程池中是否有空闲线程来执行SynchronousQueue的poll方法移除的任务。如果有,则配对成功,将任务交给这个空闲线程
-
否则,配对失败,创建新的线程去处理任务
-
当线程池中的线程空闲时,会执行SynchronousQueue的poll方法等待执行SynchronousQueue中新提交的任务。若等待时间超过60s,空闲线程就会终止
-
ScheduledThreadPool(定时线程池)
-
源码
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) { return new ScheduledThreadPoolExecutor(corePoolSize); } public ScheduledThreadPoolExecutor(int corePoolSize) { super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue()); }
-
源码分析
线程总数阀值为Integer.MAX_VALUE,工作队列使用DelayedWorkQueue,非核心线程存活时间为0,所以线程池仅仅包含固定数目的核心线程。
-
提交任务方式
-
scheduleAtFixedRate:按照固定速率周期执行
-
scheduleWithFixedDelay:上个任务延迟固定时间后执行
-
-
使用场景
周期性执行任务,并且需要限制线程数量的场景
-
执行流程图
线程池执行线程任务步骤
调用Executors类的静态方法创建ExecutorService对象
-
创建ExecutorService源码
//ExecutorService是一个工厂类,主要用来创建ExecutorService public static ExecutorService newFixedThreadPool(int nThreads) { //用newFixedThreadPool()这个静态方法用来创建一个可重用固定线程数的线程池 return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }
创建一个类实现Runnable接口
-
案例代码
// 创建一个实现Runnable接口的类 Thread1 class Thread1 implements Runnable { @Override public void run() { for (int i = 0; i <= 50; i++) { if (i % 2 == 0) { System.out.println(Thread.currentThread().getName() + "的i值为: " + i); } } } } // 创建一个实现Runnable接口的类 Thread2 class Thread2 implements Runnable { @Override public void run() { for (int i = 0; i <= 50; i++) { if (i % 2 != 0) { System.out.println(Thread.currentThread().getName() + "的i值为: " + i); } } } }
调用ExecutorService对象的execute()方法执行Runnable接口实例
调用ExecutorService对象的shutdown()方法关闭线程池
-
案例源码
public class ThreadPool { public static void main(String[] args) { // 创建一个固定线程数的的线程池 ExecutorService pool = Executors.newFixedThreadPool(10); // 向线程中提交两个任务,执行指定的线程的操作。需要提供实现Runnable接口实现类的对象 pool.execute(new Thread1()); pool.execute(new Thread2()); // 关闭线程池 pool.shutdown(); } }
-
输出结果
pool-1-thread-2的i值为: 1 pool-1-thread-2的i值为: 3 pool-1-thread-2的i值为: 5 pool-1-thread-2的i值为: 7 pool-1-thread-2的i值为: 9 pool-1-thread-2的i值为: 11 pool-1-thread-2的i值为: 13 pool-1-thread-2的i值为: 15 pool-1-thread-2的i值为: 17 pool-1-thread-2的i值为: 19 pool-1-thread-2的i值为: 21 pool-1-thread-2的i值为: 23 pool-1-thread-2的i值为: 25 pool-1-thread-2的i值为: 27 pool-1-thread-2的i值为: 29 pool-1-thread-2的i值为: 31 pool-1-thread-2的i值为: 33 pool-1-thread-2的i值为: 35 pool-1-thread-2的i值为: 37 pool-1-thread-2的i值为: 39 pool-1-thread-2的i值为: 41 pool-1-thread-2的i值为: 43 pool-1-thread-2的i值为: 45 pool-1-thread-2的i值为: 47 pool-1-thread-2的i值为: 49 pool-1-thread-1的i值为: 0 pool-1-thread-1的i值为: 2 pool-1-thread-1的i值为: 4 pool-1-thread-1的i值为: 6 pool-1-thread-1的i值为: 8 pool-1-thread-1的i值为: 10 pool-1-thread-1的i值为: 12 pool-1-thread-1的i值为: 14 pool-1-thread-1的i值为: 16 pool-1-thread-1的i值为: 18 pool-1-thread-1的i值为: 20 pool-1-thread-1的i值为: 22 pool-1-thread-1的i值为: 24 pool-1-thread-1的i值为: 26 pool-1-thread-1的i值为: 28 pool-1-thread-1的i值为: 30 pool-1-thread-1的i值为: 32 pool-1-thread-1的i值为: 34 pool-1-thread-1的i值为: 36 pool-1-thread-1的i值为: 38 pool-1-thread-1的i值为: 40 pool-1-thread-1的i值为: 42 pool-1-thread-1的i值为: 44 pool-1-thread-1的i值为: 46 pool-1-thread-1的i值为: 48 pool-1-thread-1的i值为: 50
其他
编码规范
-
阿里巴巴编码规范不推荐用Executors来创建ThreadPoolExecutor
以创建newFixedThreadPool为例
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), threadFactory); }
其中里面设置的corePoolSize(核心线程池大小)和maximumPoolSize(最大线程数)都是nThreads,其设定的阻塞队里是无界的,则饱和策略将失效,所有请求将一直排队等待被执行,可能会产生内存溢出的风险。
//手动创建线程池 ExecutorService poolExecutor = new ThreadPoolExecutor(10, 15, 24, TimeUnit.HOURS, new LinkedBlockingDeque<>(10));
ThreadPoolExecutor源码解析
-
继承结构图
-
主要成员变量
public class ThreadPoolExecutor extends AbstractExecutorService { private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); // 表示线程池数量的位数,很明显是29,Integer.SIZE=32 private static final int COUNT_BITS = Integer.SIZE - 3; // 表示线程池最大数量,2^29 - 1 private static final int CAPACITY = (1 << COUNT_BITS) - 1; // runState is stored in the high-order bits 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; // Packing and unpacking ctl private static int runStateOf(int c) { return c & ~CAPACITY; } private static int workerCountOf(int c) { return c & CAPACITY; } private static int ctlOf(int rs, int wc) { return rs | wc; } /* * Bit field accessors that don't require unpacking ctl. * These depend on the bit layout and on workerCount being never negative. */ private static boolean runStateLessThan(int c, int s) { return c < s; } private static boolean runStateAtLeast(int c, int s) { return c >= s; } private static boolean isRunning(int c) { return c < SHUTDOWN; } /** * Attempts to CAS-increment the workerCount field of ctl. */ private boolean compareAndIncrementWorkerCount(int expect) { return ctl.compareAndSet(expect, expect + 1); } /** * Attempts to CAS-decrement the workerCount field of ctl. */ private boolean compareAndDecrementWorkerCount(int expect) { return ctl.compareAndSet(expect, expect - 1); } /** * Decrements the workerCount field of ctl. This is called only on * abrupt termination of a thread (see processWorkerExit). Other * decrements are performed within getTask. */ private void decrementWorkerCount() { do {} while (! compareAndDecrementWorkerCount(ctl.get())); } /** * The queue used for holding tasks and handing off to worker * threads. We do not require that workQueue.poll() returning * null necessarily means that workQueue.isEmpty(), so rely * solely on isEmpty to see if the queue is empty (which we must * do for example when deciding whether to transition from * SHUTDOWN to TIDYING). This accommodates special-purpose * queues such as DelayQueues for which poll() is allowed to * return null even if it may later return non-null when delays * expire. */ // 用于存放线程任务的阻塞队列 private final BlockingQueue<Runnable> workQueue; /** * Lock held on access to workers set and related bookkeeping. * While we could use a concurrent set of some sort, it turns out * to be generally preferable to use a lock. Among the reasons is * that this serializes interruptIdleWorkers, which avoids * unnecessary interrupt storms, especially during shutdown. * Otherwise exiting threads would concurrently interrupt those * that have not yet interrupted. It also simplifies some of the * associated statistics bookkeeping of largestPoolSize etc. We * also hold mainLock on shutdown and shutdownNow, for the sake of * ensuring workers set is stable while separately checking * permission to interrupt and actually interrupting. */ // 重入锁 private final ReentrantLock mainLock = new ReentrantLock(); /** * Set containing all worker threads in pool. Accessed only when * holding mainLock. */ // 线程池当中的线程集合,只有当拥有mainLock锁的时候,才可以进行访问 private final HashSet<Worker> workers = new HashSet<Worker>(); /** * Wait condition to support awaitTermination */ // 等待条件支持终止 private final Condition termination = mainLock.newCondition(); /** * Tracks largest attained pool size. Accessed only under * mainLock. */ private int largestPoolSize; /** * Counter for completed tasks. Updated only on termination of * worker threads. Accessed only under mainLock. */ private long completedTaskCount; /* * All user control parameters are declared as volatiles so that * ongoing actions are based on freshest values, but without need * for locking, since no internal invariants depend on them * changing synchronously with respect to other actions. */ /** * Factory for new threads. All threads are created using this * factory (via method addWorker). All callers must be prepared * for addWorker to fail, which may reflect a system or user's * policy limiting the number of threads. Even though it is not * treated as an error, failure to create threads may result in * new tasks being rejected or existing ones remaining stuck in * the queue. * * We go further and preserve pool invariants even in the face of * errors such as OutOfMemoryError, that might be thrown while * trying to create threads. Such errors are rather common due to * the need to allocate a native stack in Thread.start, and users * will want to perform clean pool shutdown to clean up. There * will likely be enough memory available for the cleanup code to * complete without encountering yet another OutOfMemoryError. */ // 创建新线程的线程工厂 private volatile ThreadFactory threadFactory; /** * Handler called when saturated or shutdown in execute. */ // 饱和策略 private volatile RejectedExecutionHandler handler; }
-
核心参数
ctl是主要的控制状态:
-
workerCount:表示有效的线程数目
-
runState:表示线程池里线程的运行状态
-
-
重要方法
- execute方法
// 调用execute方法将线程提交到线程池中 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(); // 如果workerCount值小于corePoolSize if (workerCountOf(c) < corePoolSize) { // 添加任务到worker集合当中,成功的话返回 if (addWorker(command, true)) return; // 如果失败,再次获取线程池的控制状态 c = ctl.get(); } // 如果corePoolSize已经满了,则需要加入到阻塞队列 // 判断线程池的状态以及是否可以往阻塞队列中继续添加runnable if (isRunning(c) && workQueue.offer(command)) { // 获取线程池的状态 int recheck = ctl.get(); // 再次检查状态,线程池不处于RUNNING状态,将任务从workQueue队列中移除 if (! isRunning(recheck) && remove(command)) reject(command); else if (workerCountOf(recheck) == 0) addWorker(null, false); } // 如果此时队列已满,则会采取相应的拒绝策略 else if (!addWorker(command, false)) reject(command); }
总结
(1) 往corePoolSize中加入任务进行执行
(2) 当corePoolSize满时,往阻塞队列中加入任务
(3) 阻塞队列满时并且maximumPoolSize已满,则采取相应的拒绝策略
- addWorker()方法
private boolean addWorker(Runnable firstTask, boolean core) { // 外部循环标志 retry: for (;;) { // 获取线程池的状态 int c = ctl.get(); int rs = runStateOf(c); // Check if queue empty only if necessary. if (rs >= SHUTDOWN && ! (rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty())) return false; for (;;) { // 获取workerCount int wc = workerCountOf(c); if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize)) return false; // 跳出循环 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 { // 实例化worker对象 w = new Worker(firstTask); // 获取worker的线程 final Thread t = w.thread; // 如果获取的线程不为空 if (t != null) { // 初始线程池的锁 final ReentrantLock mainLock = this.mainLock; // 得到锁 mainLock.lock(); try { // Recheck while holding lock. // Back out on ThreadFactory failure or if // shut down before lock acquired. // 获取锁后再次检查,获取线程池runState int rs = runStateOf(ctl.get()); // 判断 if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)) { // 检查线程是否启动 if (t.isAlive()) // precheck that t is startable // 如果未启动存活,则抛出异常 throw new IllegalThreadStateException(); // 往corePoolSize中加入任务 workers.add(w); // 获取workers集合的大小 int s = workers.size(); // 如果大小超过largestPoolSize if (s > largestPoolSize) // 重新设置线程池拥有最大线程数的大小 largestPoolSize = s; // 改变状态 workerAdded = true; } } finally { // 释放锁 mainLock.unlock(); } if (workerAdded) { // 运行 t.start(); // 改变状态 workerStarted = true; } } } finally { // 如果worker没有启动成功 if (! workerStarted) addWorkerFailed(w); } // 返回worker是否成功启动的标记 return workerStarted; }
参考链接
https://blog.csdn.net/zycxnanwang/article/details/105563351
https://blog.csdn.net/weixin_43570367/article/details/105077014