做网站页面怎么做a站播放量最高的视频

web/2025/10/7 20:02:26/文章来源:
做网站页面怎么做,a站播放量最高的视频,自贡跨省特大虚假广告案,建设工程施工承包合同王有志#xff0c;一个分享硬核 Java 技术的互金摸鱼侠 加入 Java 人的提桶跑路群#xff1a;共同富裕的Java人 今天是《面霸的自我修养》第 6 篇文章#xff0c;我们一起来看看面试中会问到哪些关于线程池的问题吧。数据来源#xff1a; 大部分来自于各机构#xff08;J… 王有志一个分享硬核 Java 技术的互金摸鱼侠 加入 Java 人的提桶跑路群共同富裕的Java人 今天是《面霸的自我修养》第 6 篇文章我们一起来看看面试中会问到哪些关于线程池的问题吧。数据来源 大部分来自于各机构Java 之父Java 继父某灵某泡某客以及各博主整理文档小部分来自于我以及身边朋友的实际经历题目上会做出标识并注明面试公司。 叠“BUFF” 八股文通常出现在面试的第一二轮是“敲门砖”但仅仅掌握八股文并不能帮助你拿下 Offer由于本人水平有限文中难免出现错误还请大家以批评指正为主尽量不要喷~~ 线程池是什么为什么要使用线程池 :::info 难易程度 ::: :::warning 重要程度 ::: :::success 面试公司无 ::: 计算机中线程的创建和销毁开销较大频繁的创建和销毁线程会影响程序性能。利用基于池化思想的线程池来统一管理和分配线程复用已创建的线程避免频繁创建和销毁线程带来的资源消耗提高系统资源的利用率。线程池具有以下 3 点优势 降低资源消耗重复利用已经创建的线程避免线程创建与销毁带来的资源消耗提高响应速度接收任务时可以通过线程池直接获取线程避免了创建线程带来的时间消耗便于管理线程统一管理和分配线程避免无限制创建线程另外可以引入线程监控机制。 Java 中如何创建线程池 :::info 难易程度 ::: :::warning 重要程度 ::: :::success 面试公司无 ::: Java 中可以通过 ThreadPoolExecutor 和 Executors 创建线程池。 使用 ThreadPoolExecutor 创建线程池 使用 ThreadPoolExecutor 可以创建自定义线程池例如 ExecutorService threadPoolExecutor new ThreadPoolExecutor(10, 20, 10, TimeUnit.MILLISECONDS, new LinkedBlockingQueueRunnable(),new ThreadPoolExecutor.AbortPolicy() );了解以上代码的含义前我们先来看 ThreadPoolExecutor 提供的的构造方法 public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueueRunnable workQueue) {this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, Executors.defaultThreadFactory(), defaultHandler); }public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueueRunnable workQueue, ThreadFactory threadFactory) {this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, defaultHandler); }public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueueRunnable workQueue, RejectedExecutionHandler handler) {this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, Executors.defaultThreadFactory(), handler); }public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueueRunnable 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 个构造方法但最后都会指向含有 7 个参数的构造方法上。我们一一说明这些参数的含义 int corePoolSize线程池的核心线程数量核心线程在线程池的生命周期中不会被销毁int maximumPoolSize线程池的最大线程数量超出核心线程数量的非核心线程long keepAliveTime线程存活时间非核心线程空闲时存活的最大时间TimeUnit unitkeepAliveTime 的时间单位BlockingQueueRunnable workQueue线程池的任务队列ThreadFactory threadFactory线程工厂用于创建线程可以自定义线程RejectedExecutionHandler handler拒绝策略当任务数量超出线程池的容量超过 maximumPoolSize 并且 workQueue 已满时的处理策略。 使用 Executors 创建线程池 除了使用 ThreadPoolExecutor 创建线程池外还可以通过 Executors 创建 Java 内置的线程池Java 中提供了 6 种内置线程池 ExecutorService fixedThreadPool Executors.newFixedThreadPool(10);ExecutorService singleThreadExecutor Executors.newSingleThreadExecutor();ExecutorService cachedThreadPool Executors.newCachedThreadPool();ExecutorService scheduledExecutorService Executors.newScheduledThreadPool(10);ExecutorService singleThreadScheduledExecutor Executors.newSingleThreadScheduledExecutor();ExecutorService workStealingPool Executors.newWorkStealingPool();Tips关于这 6 种线程池的详细解释参考下一题。 Java 中提供了哪些线程池 :::info 难易程度 ::: :::warning 重要程度 ::: :::success 面试公司无 ::: Java 中提供了 6 种线程池可以通过 Executors 获取。 FixedThreadPool 通过 Executors 创建 FixedThreadPool 的代码如下 ExecutorService fixedThreadPool Executors.newFixedThreadPool(10);FixedThreadPool 是固定线程数量的线程池通过Executors#newFixedThreadPool方法获得源码如下 public static ExecutorService newFixedThreadPool(int nThreads) {return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueueRunnable()); }本质上是通过 ThreadPoolExecutor 创建的线程池核心线程数和最大线程数相同工作队列使用 LinkedBlockingQueue该队列最大容量是 Integer.MAX_VALUE。 SingleThreadExecutor 通过 Executors 创建 SingleThreadExecutor 的代码如下 ExecutorService singleThreadExecutor Executors.newSingleThreadExecutor();SingleThreadExecutor 是只有一个线程的线程池通过Executors#newSingleThreadExecutor方法获得其源码如下 public static ExecutorService newSingleThreadExecutor() {return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueueRunnable())); }依旧是通过 ThreadPoolExecutor 创建的线程池最大线程数和核心线程数设置为 1工作队列使用 LinkedBlockingQueue。SingleThreadExecutor 适合按顺序执行的场景。 ScheduledThreadPool 通过 Executors 创建 ScheduledThreadPool 的代码如下 ScheduledExecutorService scheduledThreadPool Executors.newScheduledThreadPool(10);ScheduledThreadPool 是具有定时调度和延迟调度能力的线程池通过Executors#newScheduledThreadPool方法获得其源码如下 public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {return new ScheduledThreadPoolExecutor(corePoolSize); }与前两个不同的是 ScheduledThreadPool 是用过 ScheduledThreadPoolExecutor 创建的源码如下 public class Executors {public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {return new ScheduledThreadPoolExecutor(corePoolSize);} }public class ScheduledThreadPoolExecutor extends ThreadPoolExecutor implements ScheduledExecutorService {public ScheduledThreadPoolExecutor(int corePoolSize) {super(corePoolSize, Integer.MAX_VALUE, DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS, new DelayedWorkQueue());} }追根溯源的话 ScheduledThreadPoolExecutor 依旧是通过 ThreadPoolExecutor 的构造方法创建线程池的能够实现定时调度的特性是因为ScheduledThreadPoolExecutor#execute方法和ScheduledThreadPoolExecutor#schedule方法实现的 public class ScheduledThreadPoolExecutor extends ThreadPoolExecutor implements ScheduledExecutorService {public void execute(Runnable command) {schedule(command, 0, NANOSECONDS);}public ScheduledFuture? schedule(Runnable command, long delay, TimeUnit unit) {if (command null || unit null)throw new NullPointerException();RunnableScheduledFutureVoid t decorateTask(command, new ScheduledFutureTaskVoid(command, null, triggerTime(delay, unit), sequencer.getAndIncrement()));delayedExecute(t);return t;} }因为 ScheduledThreadPoolExecutor 并不是线程池中的重点内容这里我们不过多讨论源码的实现我们接下来看 ScheduledThreadPoolExecutor 该如何使用 SimpleDateFormat simpleDateFormat new SimpleDateFormat(yyyy-MM-dd hh:mm:ss); System.out.println(当前时间 simpleDateFormat.format(new Date()));scheduledThreadPool.schedule(new Runnable() {Overridepublic void run() {System.out.println(执行时间 simpleDateFormat.format(new Date()) 延迟3秒执行);} }, 3, TimeUnit.SECONDS);scheduledThreadPool.scheduleAtFixedRate(new Runnable() {Overridepublic void run() {System.out.println(执行时间: simpleDateFormat.format(new Date()) 每3秒执行一次);} }, 0, 3, TimeUnit.SECONDS);SingleThreadScheduledExecutor 通过 Executors 创建 ScheduledExecutorService 的代码如下 ExecutorService singleThreadScheduledExecutor Executors.newSingleThreadScheduledExecutor();与 ScheduledThreadPool 相同 SingleThreadScheduledExecutor 也是具有定时调度和延迟调度能力的线程池同样的 SingleThreadScheduledExecutor 也是通过 ScheduledThreadPoolExecutor 创建的不同之处在于 ScheduledThreadPool 并不限制核心线程的数量而 SingleThreadScheduledExecutor 只会创建一个核心线程。 CachedThreadPool 通过 Executors 创建 CachedThreadPool 的代码如下 ExecutorService cachedThreadPool Executors.newCachedThreadPool();CachedThreadPool 是可缓存线程的线程池通过Executors#newSingleThreadExecutor方法获得其源码如下 public static ExecutorService newCachedThreadPool() {return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueueRunnable()); }CachedThreadPool 的特点是没有核心线程任务提交后会创建新线程执行并且没有最大数量限制每个线程空闲后的存活时间是 60 秒如果 60 秒内有新的任务提交会复用这些线程也就实现了线程缓存的能力工作队列使用 SynchronousQueue它不存储任何元素使用 SynchronousQueue 的目的是为了能够立即创建使用非核心线程涉及到 ThreadPoolExecutor 的实现原理后文详解执行任务。 WorkStealingPool 通过 Executors 创建 ScheduledExecutorService 的代码如下 ExecutorService workStealingPool Executors.newWorkStealingPool();WorkStealingPool 是 Java 8 中加入的线程池与之前的 5 种线程池直接或间接的回归到 ThreadPoolExecutor 不同WorkStealingPool 是通过 ForkJoinPool 实现的ForkJoinPool 与 ThreadPoolExecutor 都是 AbstractExecutorService 的实现内部通过 Work-Stealing 算法并行的处理任务但无法保证任务的执行顺序。 Executor 框架是什么 :::info 难易程度 ::: :::warning 重要程度 ::: :::success 面试公司无 ::: Executor 用于执行 RunnableCallable任务提供了将 RunnableCallable 与运行机制解耦的能力屏蔽了线程创建调度方式等。Java 中的注释是这样描述 Executor 的 An object that executes submitted Runnable tasks. This interface provides a way of decoupling task submission from the mechanics of how each task will be run, including details of thread use, scheduling, etc. An Executor is normally used instead of explicitly creating threads. Executor 的体系Executor 体系中 ExecutorService 接口对 Executor 进行了扩展提供了管理 Executor 和查看 Executor 状态的能力。 An Executor that provides methods to manage termination and methods that can produce a Future for tracking progress of one or more asynchronous tasks. 另外我把 Executors 也加入到了 Executor 的体系结构中虽然没有继承或者实现的关系但是根据 Java 中的命名规范Executors 是作为 Executor 的工具类出现的从类图中可以看到 Executors 提供了 线程池都有哪些状态 :::info 难易程度 ::: :::warning 重要程度 ::: :::success 面试公司无 ::: Java 中定义了线程池的 5 种状态 private static final int RUNNING -1 COUNT_BITS; // 111 0 0000 0000 0000 0000 0000 0000 0000 private static final int SHUTDOWN 0 COUNT_BITS; // 000 0 0000 0000 0000 0000 0000 0000 0000 private static final int STOP 1 COUNT_BITS; // 001 0 0000 0000 0000 0000 0000 0000 0000 private static final int TIDYING 2 COUNT_BITS; // 010 0 0000 0000 0000 0000 0000 0000 0000 private static final int TERMINATED 3 COUNT_BITS; // 011 0 0000 0000 0000 0000 0000 0000 0000注释中也非常详细的解释了每个状态 RUNNING: Accept new tasks and process queued tasks SHUTDOWN: Don’t accept new tasks, but process queued tasks STOP: Don’t accept new tasks, don’t process queued tasks, and interrupt in-progress tasks TIDYING: All tasks have terminated, workerCount is zero, the thread transitioning to state TIDYING will run the terminated() hook method TERMINATED: terminated() has completed RUNNING接收新任务并处理队列中的任务SHUTDOWN不接收新任务仅处理队列中的任务STOP不接收新任务不处理队列中的任务中断正在执行的任务TIDYING所有任务已经执行完毕并且工作线程为 0转换到 TIDYING 状态后将执行 Hook 方法ThreadPoolExecutor#terminatedTERMINATEDThreadPoolExecutor#terminated方法执行完毕该状态表示线程池彻底终止。 另外注释中还详细的描述了线程池状态间转换的规则 RUNNING - SHUTDOWN On invocation of shutdown() (RUNNING or SHUTDOWN) - STOP On invocation of shutdownNow() SHUTDOWN - TIDYING When both queue and pool are empty STOP - TIDYING When pool is empty TIDYING - TERMINATED When the terminated() hook method has completed RUNNING - SHUTDOWN通过调用ThreadPoolExecutor#shutdown方法RUNNING 或 SHUTDOWN - STOP通过通过调用ThreadPoolExecutor#shutdownNow方法SHUTDOWN - TIDYING工作线程为 0没有处理中的任务并且工作队列中待处理的任务为 0 时STOP - TIDYING工作线程为 0 时没有处理中的任务;TIDYING - TERMINATEDTIDYING 状态下调用ThreadPoolExecutor#terminated方法。 最后我们通过一张图来看下线程池状态之间的转换 线程池是如何实现的 :::info 难易程度 ::: :::warning 重要程度 ::: :::success 面试公司阿里巴巴美团蚂蚁金服 ::: 我们通过一段演示代码尝试着推测线程池的执行过程首先我们实现一个自定义的阻塞队列 public class CustomBlockingQueueE extends LinkedBlockingQueueE {public CustomBlockingQueue(int capacity) {super(capacity);}Overridepublic boolean offer(E e) {boolean result super.offer(e);if (result) {System.out.println(添加到队列中);}return result;} }CustomBlockingQueue 继承自 LinkedBlockingQueue并重写了LinkedBlockingQueue#offer方法在元素成功添加到队列时输出一行日志为的是能够清晰的展示线程池中任务添加到工作队列中的时机。接着我们创建线程池 ExecutorService threadPoolExecutor new ThreadPoolExecutor(3,3,10,TimeUnit.SECONDS,new CustomBlockingQueue(3),new ThreadPoolExecutor.AbortPolicy());线程池的容量是 9即最大线程 6 个核心线程 3 个非核心线程 3 个工作队列容量为 3拒绝策略是 AbortPolicy超出线程池的容量后直接丢弃任务。使用这个线程池同时执行 10 个任务 for(int i 1; i 10; i) {int finalI i;threadPoolExecutor.execute(() - {String threadName Thread.currentThread().getName();System.out.println(threadName 开始执行任务 finalI);try {// 为了能够长时间占用线程TimeUnit.SECONDS.sleep(15);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(threadName 结束执行任务 finalI);});// 为了能够实现按顺序提交任务TimeUnit.SECONDS.sleep(1); }执行上述代码可以看到打印的日志为根据执行结果并且结合之前对于线程池参数的解释我们就能推测出线程池的大致流程 编号 1~3 的任务提交到线程池后创建核心线程执行任务编号 4~6 的任务提交到线程池后因为超出核心线程的数量将任务添加到工作队列中编号 7~9 的任务提交到线程池后因为工作队列已满创建非核心线程执行任务编号 10 的任务提交到线程池后因为超出线程池的容量执行拒绝策略编号 1~3 的任务执行完毕后核心线程空闲取出工作队列中编号 4~6 的任务开始执行。 我们用一张图来展示上述代码中任务执行的顺序以上是我们通过案例来推测出的线程池工作流程接下来我们通过源码分析来印证我们推测的结果并梳理线程池执行过程中的具体细节。 线程池的源码分析 CTL 与线程池状态 分析线程池执行流程前我们先来看 ThreadPoolExecutor 中定义的主控状态 CTL 和线程池状态 private final AtomicInteger ctl new AtomicInteger(ctlOf(RUNNING, 0)); // 111 0 0000 0000 0000 0000 0000 0000 0000private static final int COUNT_BITS Integer.SIZE - 3; // 29 private static final int COUNT_MASK (1 COUNT_BITS) - 1; // 0001 1111 1111 1111 1111 1111 1111 1111// 线程池状态 private static final int RUNNING -1 COUNT_BITS; // 111 0 0000 0000 0000 0000 0000 0000 0000 private static final int SHUTDOWN 0 COUNT_BITS; // 000 0 0000 0000 0000 0000 0000 0000 0000 private static final int STOP 1 COUNT_BITS; // 001 0 0000 0000 0000 0000 0000 0000 0000 private static final int TIDYING 2 COUNT_BITS; // 010 0 0000 0000 0000 0000 0000 0000 0000 private static final int TERMINATED 3 COUNT_BITS; // 011 0 0000 0000 0000 0000 0000 0000 0000// 计算CTL private static int ctlOf(int rs, int wc) {return rs | wc; }// 获取线程池状态 private static int runStateOf(int c) { return c ~COUNT_MASK; }// 获取线程池工作线程数 private static int workerCountOf(int c) {return c COUNT_MASK; }Doug Lea 采用了位掩码技术来设计 CTL将 CTL 拆分成了 2 个部分高 3 位表示线程池状态低 29 位表示工作线程数量因此线程池最多允许 536870911 个线程处于工作状态。Java 中采用补码的形式表示数字最高位是符号位0 表示正数1 表示负数RUNNING 状态在二进制表示中最高位是 1是负数其余状态的数值均为正数。结合上一题的分析我们可以得到线程池从运行状态到终止状态随着状态的转换每种状态的数值表示是逐渐增大的。Tips如果不熟悉位运算可以参考我的另一篇文章《编程技巧“高端”的位运算》。 线程池的任务执行 我们从任务执行的入口ThreadPoolExecutor#execute的源码开始 public void execute(Runnable command) {// 校验提交的任务if (command null) {throw new NullPointerException();}// 获取CTLint c ctl.get();// STEP 1工作线程数小于核心线程数if (workerCountOf(c) corePoolSize) {if (addWorker(command, true)) {return;}// addWorker方法执行失败时重新获取CTLc ctl.get();}// STEP 2工作线程数大于核心线程数但可以添加到工作队列中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);}} // STEP 3工作队列中无法继续添加任务else if (!addWorker(command, false)) {reject(command);} }ThreadPoolExecutor#execute源码中可以将整体的执行逻辑分为 3 步见注释。详细分析这 3 步前我们先来看频繁出现的ThreadPoolExecutor#addWorker方法由于该方法的源码较长我们拆开来分析。ThreadPoolExecutor#addWorker方法的声明 private boolean addWorker(Runnable firstTask, boolean core)ThreadPoolExecutor#addWorker方法提供了两个参数 Runnable firstTask要执行的任务boolean core表示是否为核心线程 接着是ThreadPoolExecutor#addWorker方法源码的第一部分检查线程池的状态及线程的数量 retry: for (int c ctl.get();;) {// 检查线程池状态if (runStateAtLeast(c, SHUTDOWN) (runStateAtLeast(c, STOP) || firstTask ! null || workQueue.isEmpty()))return false;for (;;) {// 检查线程池的工作线程数量if (workerCountOf(c) ((core ? corePoolSize : maximumPoolSize) COUNT_MASK))return false;// 增加线程池工作线程数量if (compareAndIncrementWorkerCount(c))break retry;c ctl.get();if (runStateAtLeast(c, SHUTDOWN))continue retry;} }以上这部分代码是对线程池状态的检查以及处理主控状态 CTL我们来分析关建代码 第 4 行检查线程池“最多”处于 SHUTDOWN 状态因为只有在 SHUTDOWN 状态和 RUNNING 状态中才会创建线程执行任务第 8 行根据入参boolean core决定创建线程数量的上限是小于 corePoolSize 还是小于 maxmunPoolSize第 11 行调用ThreadPoolExecutor#compareAndIncrementWorkerCount方法来增加 CTL 中保存的工作线程数量成功后则跳出 retry 标签第 13 行如果程序运行到这里修改 CTL 中工作线程数量失败需要重新获取 CTL 并检查线程池状态。 注意ThreadPoolExecutor#compareAndIncrementWorkerCount方法采用了 CAS 技术方法返回失败说明此时 CTL 已经被其它线程修改需要重新获取 CTL 并检查线程池状态。最后来看ThreadPoolExecutor#addWorker方法的第二部分源码执行工作任务 boolean workerStarted false; boolean workerAdded false; Worker w null; try {// 创建Worker对象w new Worker(firstTask);// 获取到Worker中创建的线程final Thread t w.thread;if (t ! null) {// 使用ReentrantLock加锁final ReentrantLock mainLock this.mainLock;mainLock.lock();try {int c ctl.get();// 检查线程池状态只有RUNNING状态和STOP之下的状态允许创建线程if (isRunning(c) || (runStateLessThan(c, STOP) firstTask null)) {// 检查线程状态if (t.getState() ! Thread.State.NEW)throw new IllegalThreadStateException();workers.add(w);workerAdded true;// 记录线程池中出现的最大线程数量int s workers.size();if (s largestPoolSize)largestPoolSize s;}} finally {mainLock.unlock();}if (workerAdded) {// 启动线程开始执行任务t.start();workerStarted true;}} } finally {if (! workerStarted)addWorkerFailed(w); } return workerStarted;以上这部分代码是创建线程并执行任务的方法我们来分析关键部分 第 6 行创建 Worker 对象我们可以直接将 Worker 对象与 Thread 对象画上等号认为创建了 Worker 对象就是创建了线程。第 16 行依旧是对线程池状态的检查这里验证了线程池状态“最多”处于 SHUTDOWN 状态这个限制也不难理解RUNNGING 状态是线程池的正常状态允许创建线程并处理任务SHUTDOWN 状态下允许处理工作队列中的线程如果线程池中没有活跃的线程需要创建线程来处理。 需要注意这部分代码中出现的ThreadPoolExecutor#runStateLessThan方法与之前出现的ThreadPoolExecutor#runStateAtLeast方法在比较 CTL 与状态时在开闭区间上存在差异。到这里整个ThreadPoolExecutor#addWorker的方法也就差不多分析完了它实际上就做了两件事 检查线程池包括状态检查线程池中线程数量的检查创建 Worker 对象并启动相当于创建 Thread 对象并启动。 回过头来我们结合对ThreadPoolExecutor#addWorker方法的分析不难看出ThreadPoolExecutor#execute方法的 3 步都做了什么 步骤线程池状态工作线程数队列状态处理方式STEP 1RUNNING corePoolSize空核心线程数未满创建核心线程处理任务STEP2RUNNING corePoolSize未饱和核心线程数已满将任务添加到队列中STEP3RUNNING corePoolSize已饱和核心线程和工作队列已满创建非核心线程处理任务创建失败则执行拒绝策略 至此我们已经能够清晰的看到线程池的执行流程了并且也可以印证我们通过演示代码来推测的线程池执行流程的正确性了。 线程池中的线程是什么时间创建的 :::info 难易程度 ::: :::warning 重要程度 ::: :::success 面试公司无 ::: 在上一题的分析中我们已经知道线程池的线程是在提交任务后通过ThreadPoolExecutor#addWorker方法中创建的具体是在 Worker 的构造方法中 Worker(Runnable firstTask) {setState(-1);this.firstTask firstTask;this.thread getThreadFactory().newThread(this); }每个 Worker 对象中都持有一个通过 ThreadFactory 创建的线程这也是为什么我将线程池中的线程与 Worker 对象画上等号。除此之外还可以调用ThreadPoolExecutor#prestartCoreThread来预创建线程该方法源码如下 public boolean prestartCoreThread() {return workerCountOf(ctl.get()) corePoolSize addWorker(null, true); }从源码中可以看到每次调用只会预创建 1 个 Worker 对象即 1 个核心线程如果需要创建全部的核心线程需要多次调用。 线程池中的核心线程是如何复用的 :::info 难易程度 ::: :::warning 重要程度 ::: :::success 面试公司阿里巴巴美团蚂蚁金服 ::: 线程池是通过 Worker 对象来完成线程的复用的。我们来看内部类 Worker 的类型声明 private final class Worker extends AbstractQueuedSynchronizer implements Runnable;Worker 自身继承了 AQS并实现了 Runnable 接口说明 Worker 自身就是可被线程执行的。接下来是 Worker 的构造方法 Worker(Runnable firstTask) {setState(-1);this.firstTask firstTask;this.thread getThreadFactory().newThread(this); }可以看到构造在创建线程时使用的 Runnable 对象是 Worker 对象自身同时 Worker 对象通过成员变量 firstTask 保存了我们提交的 Runnable 对象。回到ThreadPoolExecutor#addWorker方法中我们来看线程启动的部分 w new Worker(firstTask); final Thread t w.thread; t.start();我们已经知道Java 中调用Thread#start方法中会执行Runnable#run方法那么在这段代码中执行的是谁的 run 方法呢答案是 Worker 对象重写的 run 方法。我们来看Worker#run的源码 private final class Worker extends AbstractQueuedSynchronizer implements Runnable {public void run() {runWorker(this);} }Worker#run方法调用了 runWorker 方法该方法并不是内部类 Worker 的而是 ThreadPoolExecutor 的方法接着来看源码 final void runWorker(Worker w) {Thread wt Thread.currentThread();// 获取Worker对象中封装的RunnableRunnable task w.firstTask;w.firstTask null;w.unlock();boolean completedAbruptly true;try {while (task ! null || (task getTask()) ! null) {w.lock();// 线程池状态检查if ((runStateAtLeast(ctl.get(), STOP) || (Thread.interrupted() runStateAtLeast(ctl.get(), STOP))) !wt.isInterrupted())wt.interrupt();try {beforeExecute(wt, task);try {// 执行任务task.run();afterExecute(task, null);} catch (Throwable ex) {afterExecute(task, ex);throw ex;}} finally {task null;w.completedTasks;w.unlock();}}completedAbruptly false;} finally {processWorkerExit(w, completedAbruptly);} }重点关注第 9 行的 while 循环它有两个条件 task ! null即 Worker 对象自身保存的 Runnable 不为 null(task getTask()) ! null即通过ThreadPoolExecutor#getTask方法获取到的 Runnable 不为 null。 第一个条件不难想到即首次提交时 Worker 会“携带” Runnable那么第二个条件是从哪里获取到的待执行任务呢答案是工作队列。我们来看ThreadPoolExecutor#getTask方法的源码 private Runnable getTask() {boolean timedOut false;for (;;) {// 状态检查int c ctl.get();if (runStateAtLeast(c, SHUTDOWN) (runStateAtLeast(c, STOP) || workQueue.isEmpty())) {decrementWorkerCount();return null;}// 获取工作线程数量并判断是否为非核心线程int wc workerCountOf(c);boolean timed allowCoreThreadTimeOut || wc corePoolSize;if ((wc maximumPoolSize || (timed timedOut)) (wc 1 || workQueue.isEmpty())) {if (compareAndDecrementWorkerCount(c))return null;continue;}try {// 通过工作队列获取待执行任务Runnable r timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take();if (r ! null)return r;timedOut true;} catch (InterruptedException retry) {timedOut false;}} }先来关注第 12 行中变量 timed 的赋值逻辑有两个条件 boolean allowCoreThreadTimeOutThreadPoolExecutor 的成员变量可以通过ThreadPoolExecutor#allowCoreThreadTimeOut方法赋值表示是否允许核心线程超时销毁默认值是 falsewc corePoolSize即工作线程数量是否超出核心线程的数量限制。 timed 变量关系到第 20 行代码中获取工作队列中待执行任务的方式 如果允许销毁核心线程或工作线程数量已经超出核心线程数量限制则通过workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS)的方式获取待执行任务该方法获取队首元素如果当前队列为空则在等待指定时间后返回 null如果不允许销毁核心线程或工作线程数量小于核心线程数量则通过workQueue.take()的方式获取待执行任务该方法获取队首元素如果队列为空则进入等待直到能够获取元素为止。 知道了以上内容后我们再来看ThreadPoolExecutor#runWorker方法的 while 循环在不允许销毁核心线程的线程池中核心线程第一次执行 Worker 自身“携带”的任务执行完毕后再次进入 while 循环尝试获取工作队列中的待执行任务如果此时工作队列中没有待执行任务则工作队列进入阻塞此时 runWorker 方法也进入阻塞直到工作队列能够成功返回待执行任务。简单来说线程池的核心线程复用在于核心线程启动后就进入“不眠不休”的工作中除了执行首次提交的任务外还会不断尝试从工作队列中获取待执行任务如果无法获取就阻塞到能够获取任务为止。 线程池的非核心线程是什么时候销毁的 :::info 难易程度 ::: :::warning 重要程度 ::: :::success 面试公司无 ::: 非核心线程的销毁依旧是在ThreadPoolExecutor#runWorker方法中进行的。我们回到ThreadPoolExecutor#getTask方法中 private Runnable getTask() {// 标记是否超时boolean timedOut false;for (;;) {int c ctl.get();if (runStateAtLeast(c, SHUTDOWN) (runStateAtLeast(c, STOP) || workQueue.isEmpty())) {decrementWorkerCount();return null;}int wc workerCountOf(c);boolean timed allowCoreThreadTimeOut || wc corePoolSize;if ((wc maximumPoolSize || (timed timedOut)) (wc 1 || workQueue.isEmpty())) {if (compareAndDecrementWorkerCount(c))return null;continue;}try {Runnable r timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take();if (r ! null)return r;timedOut true;} catch (InterruptedException retry) {timedOut false;}} }假设现在的情况是非核心线程通过ThreadPoolExecutor#getTask方法获取工作队列中的待执行任务而此时工作队列中已经没有待执行的任务了。第一次进入循环时第 11 行代码会因为wc corePoolSize而将 timed 赋值为 true此时wc maximumPoolSize且 timedOut 为 false并不满足第 12 行的判断条件会直接来到第 18 行通过workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS)的方式从工作队列中获取待执行任务在等待一定的时间后工作队列返回了 null此时进入第 21 行将 timedOut 赋值为 true 后再次进入循环。第二次进入循环时变量 timed 依旧被赋值为 true与第一次进入循环不同的是此时的 timedOut 为 ture已经满足第 12 行的判断条件执行 if 语句的内容调用ThreadPoolExecutor#compareAndDecrementWorkerCount方法减少 CTL 中工作线程的数量并且返回 null。重点在这个返回的 null 上当ThreadPoolExecutor#getTask返回 null 后ThreadPoolExecutor#runWorker会跳出循环执行 finally 中的ThreadPoolExecutor#processWorkerExit方法 private void processWorkerExit(Worker w, boolean completedAbruptly) {if (completedAbruptly) decrementWorkerCount();final ReentrantLock mainLock this.mainLock;mainLock.lock();try {completedTaskCount w.completedTasks;// 从workers中删除workerworkers.remove(w);} finally {mainLock.unlock();}// 尝试修改线程池状态tryTerminate();// 处理STOP之下的状态int c ctl.get();if (runStateLessThan(c, STOP)) {if (!completedAbruptly) {int min allowCoreThreadTimeOut ? 0 : corePoolSize;if (min 0 ! workQueue.isEmpty())min 1;if (workerCountOf(c) min)return; }addWorker(null, false);} }该方法中从 ThreadPoolExecutor 的成员变量 workers 中删除 Worker 对象后其内部的线程也将执行完毕随后会调用Thread#exit方法来销毁线程即表示线程池中该线程已经销毁。 ThreadPoolExecutor#submit 方法和 ThreadPoolExecutor#execute 方法有什么区别 :::info 难易程度 ::: :::warning 重要程度 ::: :::success 面试公司无 ::: ThreadPoolExecutor 的实现中ThreadPoolExecutor#submit和ThreadPoolExecutor#execute有 4 处区别 ThreadPoolExecutor#submitThreadPoolExecutor#execute方法定义ExecutorService 接口中定义Executor 接口中定义返回值有返回值无返回值任务类型Runnable 任务和 Callable 任务Runnable 任务实现AbstractExecutorService 中实现ThreadPoolExecutor 中实现 Java 中提供了哪些拒绝策略 :::info 难易程度 ::: :::warning 重要程度 ::: :::success 面试公司无 ::: Java 中提供了 4 种拒绝策略 CallerRunsPolicy由调用ThreadPoolExecutor#execute方法的线程执行该任务AbortPolicy直接抛出异常DiscardPolicy丢弃当前任务DiscardOldestPolicy丢弃工作队列中队首的任务即最早加入队列中的任务并将当前任务添加到队列中。 这 4 种拒绝策略被定义为 ThreadPoolExecutor 的内部类源码如下 public class ThreadPoolExecutor extends AbstractExecutorService {public static class CallerRunsPolicy implements RejectedExecutionHandler {public CallerRunsPolicy() { }public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {if (!e.isShutdown()) {r.run();}}}public static class AbortPolicy implements RejectedExecutionHandler {public AbortPolicy() { }public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {throw new RejectedExecutionException(Task r.toString() rejected from e.toString());}}public static class DiscardPolicy implements RejectedExecutionHandler {public DiscardPolicy() { }public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {}}public static class DiscardOldestPolicy implements RejectedExecutionHandler {public DiscardOldestPolicy() { }public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {if (!e.isShutdown()) {e.getQueue().poll();e.execute(r);}}} }如果 Java 内置的拒绝策略无法满足业务需求可以自定义拒绝策略只需要实现 RejectedExecutionHandler 接口即可。 什么是阻塞队列 :::info 难易程度 ::: :::warning 重要程度 ::: :::success 面试公司蚂蚁金服 ::: 阻塞队列是一种特殊的队列相较于普通的队列阻塞队列提供了一个额外的功能当队列为空时获取队列中元素的线程会被阻塞。Java 中提供了 7 种阻塞队列全部是继承自 BlockingQueue 接口BlockingQueue 接口继承了 Queue 接口而 Queue 接口继承了 Collection 接口。使用时如果需要阻塞队列的能力要选择单独定义在 BlockingQueue 接口中的方法。以下是阻塞队列在实现不同接口方法时的差异 操作方法特点Collection 接口添加元素boolean add(E e)失败时抛出异常删除元素boolean remove(Object o)失败时抛出异常Queue 接口入队操作boolean offer(E e)成功返回 true失败返回 false出队操作E poll()成功返回出队元素失败返回 nullBlockingQueue 接口入队操作void put(E e)无法入队时阻塞当前线程直到入队成功boolean offer(E e, long timeout, TimeUnit unit)无法入队时阻塞当前线程直到入队成功或超时出队操作E take()队列中无元素时阻塞当前线程直到有元素可出队E poll(long timeout, TimeUnit unit)队列中无元素时阻塞当前线程直到有元素可出队或超时 接下来我们来看这 7 种阻塞队列的特点。 ArrayBlockingQueue ArrayBlockingQueue 底层使用数组作为存储结构需要指定阻塞队列的容量数组的特点且不具备扩容能力。除此之外ArrayBlockingQueue 还提供了公平访问和非公平访问两种模式默认使用非公平访问模式。 // 非公平访问的ArrayBlockingQueue ArrayBlockingQueueRunnable arrayBlockingQueue new ArrayBlockingQueue(1000);// 公平访问模式的ArrayBlockingQueue ArrayBlockingQueueRunnable arrayBlockingQueue new ArrayBlockingQueue(1000, true);公平访问模式指的是在队列中无元素时阻塞的线程会在队列可用时按照阻塞的先后顺序访问队列而非公平访问模式下则是随机一个线程获取访问权限。公平访问模式保证了先后顺序但是为了维护这个先后顺序需要付出额外的性能作为代价。需要额外注意的是ArrayBlockingQueue 在入队和出队操作上使用同一把锁也就是说一个线程的入队操作不仅会阻塞其它线程的入队操作还会阻塞其它线程的出队操作。 LinkedBlockingQueue LinkedBlockingQueue 底层使用单向链表作为存储结构并且提供了默认的容量**Integer.MAX_VALUE**即在不指定容量时 LinkedBlockingQueue 是“无限大”的也即是常说的无界队列。 LinkedBlockingQueueRunnable linkedBlockingQueue new LinkedBlockingQueue();LinkedBlockingQueueRunnable linkedBlockingQueue new LinkedBlockingQueue(1000);LinkedBlockingQueue 内部使用两把独立的锁来控制入队和出队这意味着入队和出队操作是相互独立的并不会互相阻塞。 public class LinkedBlockingQueueE extends AbstractQueueE implements BlockingQueueE, java.io.Serializable {private final ReentrantLock takeLock new ReentrantLock();private final ReentrantLock putLock new ReentrantLock(); }PriorityBlockingQueue PriorityBlockingQueue 底层使用数组作为存储结构默认容量为 11支持自动扩容支持元素按照优先级排序可以通过自定义实现 Comparator 接口来实现排序功能。例如创建 Student 类并允许根据 studenId 进行排序 Getter Setter public class Student implements ComparableStudent {private Integer studentId;private String name;Overridepublic int compareTo(Student student) {return this.studentId.compareTo(student.studentId);}public static void main(String[] args) {PriorityBlockingQueueStudent priorityBlockingQueue new PriorityBlockingQueue();for(int i 0; i 10; i) {Random random new Random();Student student new Student();Integer studentId random.nextInt(1000);student.setStudentId(studentId);student.setName(name- studentId);priorityBlockingQueue.put(student);}} }注意PriorityBlockingQueue 在入队后并不能完全保证队列中元素的优先级顺序即队列中存储的元素可能是乱序的但在出队时是按照优先级顺序出队。 DelayQueue DelayQueue 底层使用 PriorityQueue 作为存储结构因此 DelayQueue 具有 PriorityQueue 的特点比如默认容量为 11支持自动扩容支持元素按照优先级排序。DelayQueue 自身的特点是延时获取元素即在元素创建指定时间后才能够从队列中获取元素为了实现这个功能入队的元素必须实现 Delayed 接口。我们来创建一个实现 Delayed 接口的元素 public class DelayedElement implements Delayed {private final Long createTime;private final Long delayTIme;private final TimeUnit delayTimeUnit;public DelayedElement(long delayTime, TimeUnit delayTimeUnit) {this.createTime System.currentTimeMillis();this.delayTIme delayTime;this.delayTimeUnit delayTimeUnit;}Overridepublic long getDelay(TimeUnit unit) {long duration createTime TimeUnit.MILLISECONDS.convert(this.delayTIme, this.delayTimeUnit) - System.currentTimeMillis();return unit.convert(duration, TimeUnit.MILLISECONDS);}Overridepublic int compareTo(Delayed o) {DelayedElement delayedElement (DelayedElement) o;return this.createTime.compareTo(delayedElement.createTime);}注意Delayed#getDelay方法的实现这里的返回值并不是固定的延时时间而是期望的延时时间与当前时间的差值只有差值小于等于 0 时该元素才能出队。这里我选择在构造方法中传入延时时间与时间单位并记录对象的创建时间Delayed#getDelay方法通过 创建时间延时时间-当前时间的方式计算差值。至于Comparable#compareTo方法的实现与 DelayQueue 的使用与其它阻塞队列并无差别。 SynchronousQueue SynchronousQueue 不存储元素它的每次入队操作都要对应一次出队操作否则不能继续添加元素。注意SynchronousQueue 在入队成功后会阻塞线程直到其他线程执行出队操作同样的出队操作时如果没有提前执行入队操作也会被阻塞也就是说无法在一个线程中完成 SynchronousQueue 的入队操作和出队操作。 SynchronousQueueInteger synchronousQueue new SynchronousQueue();new Thread(() - {for (int i 0; i 10; i) {try {synchronousQueue.put(i);System.out.println(put- i);} catch (InterruptedException e) {throw new RuntimeException(e);}} }).start();TimeUnit.MILLISECONDS.sleep(100);new Thread(() - {for (int i 0; i 10; i) {try {System.out.println(take- synchronousQueue.take());} catch (InterruptedException e) {throw new RuntimeException(e);}} }).start();LinkedTransferQueue LinkedTransferQueue 的底层使用单向链表作为存储结构相较于其他阻塞队列 LinkedTransferQueue 还实现了 TransferQueue 接口实现了类似于 SynchronousQueue 传递元素的功能但与 SynchronousQueue 不同的是LinkedTransferQueue 是能够存储元素的。LinkedTransferQueue 实现了 TransferQueue 接口的 3 个方法 public interface TransferQueueE extends BlockingQueueE {void transfer(E e) throws InterruptedException;boolean tryTransfer(E e);boolean tryTransfer(E e, long timeout, TimeUnit unit) throws InterruptedExceptio)throws InterruptedException; }以上 3 个方法在消费者等待获取元素调用 take 方法或 poll 方法时会立刻将元素传递给消费者它们 3 者的差异在于没有消费者等待获取元素时的处理方式 transfer 方法该方法会阻塞线程直到传入的元素被消费tryTransfer 方法该方法会直接返回 false并且放弃该元素即不会存储到队列中带有超时时间的 tryTransfer 方法等待指定的时间如果依旧没有消费者消费该元素返回 false。 我们写个例子来测试LinkedTransferQueue#tryTransfer方法 LinkedTransferQueueInteger linkedTransferQueue new LinkedTransferQueue();new Thread(() - {for (int i 0; i 10; i) {if (i 3) {linkedTransferQueue.tryTransfer(i);} else {linkedTransferQueue.put(i);}} }).start();TimeUnit.SECONDS.sleep(2);for (int i 0; i 10; i) {System.out.println(take- linkedTransferQueue.take()); }启动线程向 linkedTransferQueue 中添加元素第 4 个元素调用LinkedTransferQueue#tryTransfer来传递元素主线程等待两秒后从 linkedTransferQueue 中取出元素可以看到队列中并未存储第 4 个元素。另外如果 LinkedTransferQueue 中已经存在元素take 方法或者 poll 方法优先获取的是队首元素而不是通过LinkedTransferQueue#transfe或LinkedTransferQueue#tryTransfe传入的元素。 LinkedBlockingDeque LinkedBlockingDeque 底层使用双向链表作为存储结构与 LinkedBlockingQueue 相同LinkedBlockingDeque 的默认容量为**Integer.MAX_VALUE**不同的是 LinkedBlockingDeque 实现了 BlockingDeque 接口允许操作队首和队尾的元素。LinkedBlockingDeque 的方法中名称中含有 first 或 last 的公有方法均实现自 BlockingDeque 接口或 Deque 接口例如 public class LinkedBlockingDequeE extends AbstractQueueE implements BlockingDequeE, java.io.Serializable {public void addFirst(E e) {}public void addLast(E e) {}public boolean offerFirst(E e) {}public boolean offerLast(E e) {}public void putFirst(E e) throws InterruptedException{}public void putLast(E e) throws InterruptedException {}public E pollFirst() {}public E pollLast() {}public E takeFirst() throws InterruptedException {}public E takeLast() throws InterruptedException {} }什么是无界队列使用无界队列会出现什么问题 :::info 难易程度 ::: :::warning 重要程度 ::: :::success 面试公司无 ::: 无界队列指的是队列的容量是否为“无限大”当然这并不是真正意义上无限大而是指一个非常大的值。例如创建 LinkedBlockingQueue 时如果使用无参构造器LinkedBlockingQueue 的容量会被设置为Integer.MAX_VALUE那么它就是无界队列。LinkedBlockingQueue 构造方法源码如下 public class LinkedBlockingQueueE extends AbstractQueueE implements BlockingQueueE, java.io.Serializable {public LinkedBlockingQueue() {this(Integer.MAX_VALUE);}public LinkedBlockingQueue(int capacity) {if (capacity 0) {throw new IllegalArgumentException();}this.capacity capacity;last head new NodeE(null);} }Integer.MAX_VALUE的值约为 21 亿当程序异常时或并发较大的情况下可能会无限制的向阻塞队列中添加任务导致内存溢出。通常在设置线程池的工作队列时需要根据具体的需求来计算出较为合适的队列容量。另外在 Java 的内置线程池中部分线程池使用了 LinkedBlockingQueue 且未指定容量如Executors#newFixedThreadPool和 Executors#newSingleThreadExecutor中创建的线程池使用了未指定容量的 LinkedBlockingQueue这两个线程池中的队列即为无界队列。这也是为什么阿里巴巴在《Java 开发手册》中提示到不要使用 Java 内置线程池的一个原因。 如何合理的设置线程池的参数 :::info 难易程度 ::: :::warning 重要程度 ::: :::success 面试公司阿里巴巴美团蚂蚁金服 ::: Java 提供了 7 个参数来实现自定义线程池 int corePoolSize核心线程数量int maximumPoolSize最大线程数量long keepAliveTime非核心线程存活时间TimeUnit unit非核心线程存活时间的单位BlockingQueueRunnable workQueue阻塞队列ThreadFactory threadFactory线程工厂RejectedExecutionHandler handler拒绝策略 虽然参数众多但我们关注的重点在 corePoolSize 和 maximumPoolSize 这两个参数上。通常在 corePoolSize 的设置上会将程序区分为 IO 密集型和 CPU 密集型进行 corePoolSize 的设置 CPU 密集型 C P U 核心数 1 CPU核心数1 CPU核心数1IO 密集型 C P U 核心数 × 2 CPU核心数\times 2 CPU核心数×2 在 IO 密集型程序的 corePoolSize 设置上还有另一种方案 ( 线程等待时间 线程执行时间 1 ) × C P U 核心数 (\frac{线程等待时间}{线程执行时间} 1)\times CPU核心数 (线程执行时间线程等待时间​1)×CPU核心数。例如假设每个线程的执行时间为 1 秒而线程等待获取 CPU 的时间为 2 秒CPU 核心数为 16那么根据以上公式可以得到 ( 2 1 1 ) × 16 48 (\frac{2}{1}1)\times1648 (12​1)×1648即将 corePoolSize 设置为 48.IO 密集型程序允许设置较大的 corePoolSize 的原因是CPU 可能有大量时间都在等待 IO 操作而处于空闲状态设置较大的 corePoolSize 可以提升 CPU 的利用率但这么做的另一个问题是IO 操作的速度是远低于 CPU 的计算速度的 程序的性能瓶颈会暴露在“低效”的 IO 操作上。除了以上两种方案外美团在Java线程池实现原理及其在美团业务中的实践中也提到了一些 corePoolSize 和 maximumPoolSize 的设置方案那么以上的计算方案哪个才是线程池设置的“银剑”呢实际上以上的方案都只能应对一些特定的场景而程序往往是复杂多变且不可预估的没有哪一种理论方案可以应对所有的场景。在设置线程池时会根据以上理论公式预估出较为合理的初始设置随后通过压测来调整线程池在极端场景的合理设置。当然程序不会一直处于这种极端场景如果有能力实现线程池的监控可以根据实时情况调整线程池保证程序运行的稳定性。ThreadPoolExecutor 提供了一些方法可以用于监控并动态调整线程池 public class ThreadPoolExecutor extends AbstractExecutorService {// 设置线程工厂public void setThreadFactory(ThreadFactory threadFactory) {}// 设置聚聚策略public void setRejectedExecutionHandler(RejectedExecutionHandler handler) {}// 设置核心线程数public void setCorePoolSize(int corePoolSize) {}// 设置最大线程数public void setMaximumPoolSize(int maximumPoolSize) {}// 设置非核心线程存活时间public void setKeepAliveTime(long time, TimeUnit unit) {}// 获取正在执行任务的大致线程数量public int getActiveCount() {}// 获取线程池中曾经出现过的最大的活跃线程数public int getLargestPoolSize() {}// 获取已经完成执行和待执行的大致任务数量public long getTaskCount() {}// 获取已经完成执行的大致任务数量public long getCompletedTaskCount() {}}借助以上方法并添加对执行线程的监控可以完成线程池的动态调整以达到线程池在任何场景下都处于最佳设置中。 参考资料 一文彻底了解线程池编程技巧“高端”的位运算Java线程池实现原理及其在美团业务中的实践 如果本文对你有帮助的话还请多多点赞支持。如果文章中出现任何错误还请批评指正。最后欢迎大家关注分享硬核Java技术的金融摸鱼侠王有志我们下次再见

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/88662.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

单页网站赚钱做app推广上哪些网站

js定时器功能 定时器返回值:是一个正整数,表示由 setTimeout() 调用创建的定时器的编号。这个值可以传递给 clearTimeout() 来取消该定时器。 var st;var sp; function start(){stsetTimeout("test()",3000); //3s后执行test,返…

合肥企业网站建设专家wordpress检索

题目:有n2^k个运动员要进行循环赛。现要设计一个满足以下要求的比赛日程表: (1)每个选手必须与其他n-1个选手各赛一次 (2)每个选手一天只能赛一次 (3)循环赛一共进行n-1天 解题思路&…

温州建设监理协会网站拼多多怎么查商品排名

最新数据显示,全国各城市外商直接投资额实际使用额在过去一年中呈现了稳步增长的趋势。这一数据为研究者提供了对中国外商投资活动的全面了解,并对未来投资趋势和政策制定提供了重要参考。 首先,这一数据反映了中国各城市作为外商投资的热门目…

网站建设公司ipo长沙谷歌优化

在 Java 中,泛型通配符(?)用于表示未知类型,通常用于增强泛型的灵活性。通配符可以与上下限结合使用,以限制泛型的范围。以下是通配符及上下限的使用示例: 1. 无界通配符 (?) 无界通配符表示可以接受任意…

做平台网站怎么做的海南: 加快推进全岛封关运作

动态规划具体指的是在某些复杂问题中,将问题转化为若干个子问题,并在求解每个子问题的过程中保存已经求解的结果,以便后续使用。实际上动态规划更像是一种通用的思路,而不是具体某个算法。 在强化学习中,被用于求解值函…

制作网站规划书门户网站设计要点

如何在Spring Boot中使用gRPC 大家好,我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编,也是冬天不穿秋裤,天冷也要风度的程序猿!今天我们将探讨如何在Spring Boot应用中集成和使用gRPC,这是一种高性能…

网站广告psd中国建筑人才信息网

IDE 中的分析工具Oracle Solaris Studio IDE 提供的交互式图形分析工具可用于检查在 IDE 内部运行的项目的性能。分析工具使用 Oracle Solaris Studio 实用程序和操作系统实用程序来收集数据。可通过 "Profile Project"(分析项目)按钮使用分析工具。Monitor Project(…

如何做静态页网站营销网站搭建

Description 给定一棵有n个节点的无根树和m个操作,操作有2类: 1、将节点a到节点b路径上所有点都染成颜色c; 2、询问节点a到节点b路径上的颜色段数量(连续相同颜色被认为是同一段),如“112221”由3段组成&am…

免费建网站的平台网站赏析

前言文的文字及图片来源于网络,仅供学习、交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理。作者:【Airpython】PS:如有需要Python学习资料的小伙伴可以加点击下方链接自行获取http://t.cn/A6Zvjdun准 备 工 作在编写代码…

中国建设网站首页用电脑做服务器的建一个网站

前言 欢迎阅读 Spring MVC 系列教程的第二篇文章!在上一篇文章中,我们介绍了 Spring MVC 的基本概念和使用方法。今天,我们将深入探讨 Spring MVC 中不同的请求方式,以及如何在你的应用程序中正确地处理它们。 在 Web 开发中&am…

网站制作开发教程太突然我国突然宣布

本教程旨在系统学习 Curve DAO 项目的整体架构、核心机制、合约设计、治理逻辑与代币经济等内容,帮助开发者全面理解其设计理念及运作方式。 目录总览: 1. Curve 项目概览 • 1.1 Curve 是什么?主要解决什么问题? • 1.2 与其他…

企业的网站建设与设计论文免费开发游戏

文章目录 一、简述二、Python中的列表详解2.1 创建列表2.2 访问列表元素2.3 修改列表元素2.4 列表切片2.5 列表方法2.6 列表推导式 三、Python中的元组详解3.1 创建元组3.2 访问元组元素3.3 元组是不可变的3.4 元组切片3.5 元组方法 四、Python中的字典详解4.1 创建字典4.2 访问…

做游戏ppt下载网站有哪些内容建造师招聘网

从《高可用服务设计之二:Rate limiting 限流与降级》中的“自动降级”中,我们这边将系统遇到“危险”时采取的整套应急方案和措施统一称为降级或服务降级。想要帮助服务做到自动降级,需要先做到如下几个步骤: 可配置的降级策略&am…

个人建设视频网站兰州网站建设哪家专业

人其实很难抵制诱惑,人只能远离诱惑,所以千万不要高看自己的定力。 文章目录 一、LT和ET模式1.理解LT和ET的工作原理2.通过代码来观察LT和ET工作模式的不同3.ET模式高效的原因(fd必须是非阻塞的)4.LT和ET模式使用时的读取方式 二…

宜昌网站设计公司wordpress 站点图标

先说结论:需求还是很大,但是没有什么初级程序员能干的岗位。 游戏引擎,存储,推荐引擎,infra,各种各样的性能敏感场景。 在开始前我分享下我的经历,我刚入行时遇到一个好公司和师父,…

手机网站开发模拟器三网合一企业网站

来源:悟空智能科央行发布工作论文《区块链能做什么、不能做什么?》,论文称,不要夸大或迷信区块链的功能。区块链应用要立足实际情况。目前区块链投融资领域泡沫明显。论文从经济学角度研究了区块链的功能。首先,在给出…

网站盈利模式有哪几种网络维护费计入什么科目

文章目录JWT工具模块测试JWT工具模块 如果要想在项目之中去使用JWT技术,那么就必须结合到已有的模块之中,最佳的做法就是将JWT的相关的处理 操作做为一个自动的starter组件进行接入 1、【microcloud项目】既然要开发一个starter组件,最佳的做法就是开发…

有百度推广的网站建设网站都需要准备什么材料

目录 一、实时嵌入式操作系统 1.1 概述 1.2 什么“实时” 1.3 什么是硬实时和软实时 1.4 什么是嵌入式 1.5 什么操作系统 二、常见重量级操作系统 三、常见轻量级嵌入式操作系统 3.1 概述 3.2 FreeRTOS 3.3 uC/OS-II 3.4 RT-Thread 3.5 RT-Thread、uC/OS-II、Free…

做商务网站服务网站建立的具体步骤

学习目标: 项目 实验 学习时间: 2023.11.24-2023.12.1 学习产出: 项目 由于小程序要上线了,这周前几天都在和前端联调改bug,并且多拆分出来两张表,工作量比较大,花的时间很多。 实验 整…