热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

池流程图_面试刷题36:线程池的原理和使用方法?

线程池原理和使用在面试中被高频问到,比如阿里的面试题。下面我们针对问题来进行回答。为什么要使用线程池?线程池的使用场景有2:1࿰
81890ed212a6789821c1e66dd8bd9cf8.png

线程池原理和使用在面试中被高频问到,比如阿里的面试题。下面我们针对问题来进行回答。

为什么要使用线程池?

线程池的使用场景有2:

1, 高并发场景:比如tomcat的处理机制,内置了线程池处理http请求;

2,异步任务处理:比如spring的异步方法改造,增加@Asyn注解对应了一个线程池;

使用线程池带来的好处有4:

1, 降低系统的消耗:线程池复用了内部的线程对比处理任务的时候创建线程处理完毕销毁线程降低了线程资源消耗

2,提高系统的响应速度:任务不必等待新线程创建,直接复用线程池的线程执行

3,提高系统的稳定性:线程是重要的系统资源,无限制创建系统会奔溃,线程池复用了线程,系统会更稳定

4,提供了线程的可管理功能:暴露了方法,可以对线程进行调配,优化和监控

线程池的实现原理

线程池处理任务流程

当向线程池中提交一个任务,线程池内部是如何处理任务的?

先来个流程图,标识一下核心处理步骤:

fdea640675febefc1f759e502dfeb792.png

1,线程池内部会获取activeCount, 判断活跃线程的数量是否大于等于corePoolSize(核心线程数量),如果没有,会使用全局锁锁定线程池,创建工作线程,处理任务,然后释放全局锁;

2,判断线程池内部的阻塞队列是否已经满了,如果没有,直接把任务放入阻塞队列;

3,判断线程池的活跃线程数量是否大于等于maxPoolSize,如果没有,会使用全局锁锁定线程池,创建工作线程,处理任务,然后释放全局锁;

4,如果以上条件都满足,采用饱和处理策略处理任务。

说明:使用全局锁是一个严重的可升缩瓶颈,在线程池预热之后(即内部线程数量大于等于corePoolSize),任务的处理是直接放入阻塞队列,这一步是不需要获得全局锁的,效率比较高。

源码如下:

public void execute(Runnable command) { if (command == null) throw new NullPointerException(); int c = ctl.get(); if (workerCountOf(c)

注释没保留,注释的内容就是上面画的流程图; 代码的逻辑就是流程图中的逻辑。

线程池中的线程执行任务

执行任务模型如下:

d270a219d861e94cdbe4a06dc6338ef2.png

线程池中的线程执行任务分为以下两种情况:

1, 创建一个线程,会在这个线程中执行当前任务;

2,工作线程完成当前任务之后,会死循环从BlockingQueue中获取任务来执行;

代码如下:

private boolean addWorker(Runnable firstTask, boolean core) { retry: for (int c = ctl.get();;) { // Check if queue empty only if necessary. 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(); // Re-read ctl if (runStateAtLeast(c, SHUTDOWN)) 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; mainLock.lock(); try { // Recheck while holding lock. // Back out on ThreadFactory failure or if // shut down before lock acquired. int c = ctl.get(); if (isRunning(c) || (runStateLessThan(c, STOP) && firstTask == null)) { if (t.isAlive()) // precheck that t is startable throw new IllegalThreadStateException(); workers.add(w); int s = workers.size(); if (s > largestPoolSize) largestPoolSize = s; workerAdded = true; } } finally { //释放锁 mainLock.unlock(); } if (workerAdded) { //执行提交的任务,然后设置工作线程为启动状态 t.start(); workerStarted = true; } } } finally { if (! workerStarted) addWorkerFailed(w); } return workerStarted; }

从代码中可以看到:把工作线程增加到线程池,然后释放锁,执行完提交进来的任务之后,新建的工作线程状态为启动状态;

线程池的使用

创建线程池

创建线程池使用线程池的构造函数来创建。

/** * Creates a new {@code ThreadPoolExecutor} with the given initial * parameters. * * @param corePoolSize the number of threads to keep in the pool, even * if they are idle, unless {@code allowCoreThreadTimeOut} is set * @param maximumPoolSize the maximum number of threads to allow in the * pool * @param keepAliveTime when the number of threads is greater than * the core, this is the maximum time that excess idle threads * will wait for new tasks before terminating. * @param unit the time unit for the {@code keepAliveTime} argument * @param workQueue the queue to use for holding tasks before they are * executed. This queue will hold only the {@code Runnable} * tasks submitted by the {@code execute} method. * @param threadFactory the factory to use when the executor * creates a new thread * @param handler the handler to use when execution is blocked * because the thread bounds and queue capacities are reached * @throws IllegalArgumentException if one of the following holds:
* {&#64;code corePoolSize <0}
* {&#64;code keepAliveTime <0}
* {&#64;code maximumPoolSize <&#61; 0}
* {&#64;code maximumPoolSize

参数简单翻译过来&#xff0c;然后做一下备注&#xff1a;

199779b16f783e1405917fcb38de76d2.png

RejectedExecutionHandler分为4种&#xff1a;

Abort:直接抛出异常

Discard:静默丢弃最后的任务

DiscardOldest:静默丢弃最先入队的任务&#xff0c;并处理当前任务

CallerRuns:调用者线程来执行任务

也可以自定义饱和策略。实现RejectedExecutionHandler即可。

线程池中提交任务

线程池中提交任务的方法有2&#xff1a; 1&#xff0c;void execute(Runable) ,没有返回值&#xff0c;无法判断任务的执行状态。

2&#xff0c;Future submit(Callable) ,有返回值&#xff0c;可以根据返回的Future对象来判断任务的执行状态&#xff0c;也可以调用get方法来同步阻塞当前线程获取结果&#xff0c;或者采用get方法的超时版本&#xff0c;防止阻塞超时的发生。

代码如下&#xff1a;

public interface Executor { /** * Executes the given command at some time in the future. The command * may execute in a new thread, in a pooled thread, or in the calling * thread, at the discretion of the {&#64;code Executor} implementation. * * &#64;param command the runnable task * &#64;throws RejectedExecutionException if this task cannot be * accepted for execution * &#64;throws NullPointerException if command is null */ void execute(Runnable command);}

Future submit(Callable task);

关闭线程池

关闭线程池方法有2&#xff1a;

1,shutdown();

2,shutdownNow();

两种关闭的方法区别如下表&#xff1a;

f8b021adbf59a69991d5dcdda4253220.png

关闭原理都是调用线程的interrupt()方法来中断所有的工作线程&#xff0c;所以无法中断的线程的任务可能永远没法终止。

只要调用了以上两个方法&#xff0c;isShutdown&#61;true;只有所有的工作线程都关闭&#xff0c;isTerminaed&#61;true;

如何合理的配置线程池参数&#xff1f;

分如下场景&#xff0c;参考选择依据如下&#xff1a;

2e230ac624af3f7ade58448851e1e3c7.png

队列的使用推荐使用有界队列&#xff0c;提高系统的稳定性和预警能力。

监控线程池

场景&#xff1a;当线程池出现问题&#xff0c;可以根据监控数据快速定位和解决问题。

线程池提供的主要监控参数&#xff1a;

027b9708debd9dfdb57e3b2b4a0f604c.png

也可以自定义监控,通过自定义线程池&#xff0c;实现beforeExecute,afterExecute,terminated方法&#xff0c;可以在任务执行前&#xff0c;任务执行后&#xff0c;线程池关闭前记录监控数据。

小结

本篇先从使用场景和优点出发分析了为什么要使用线程池。

然后介绍了线程池中任务的执行过程&#xff0c;以及工作线程处理任务的两种方式。

最后介绍了如何使用线程池&#xff0c;创建&#xff0c;销毁&#xff0c;提交任务&#xff0c;监控&#xff0c;设置合理的参数调优等方面。

我会持续分享Java软件编程知识和程序员发展职业之路&#xff0c;欢迎关注&#xff01; 原创不易&#xff0c;点赞关注支持一下吧&#xff01;转载请注明出处&#xff0c;让我们互通有无&#xff0c;共同进步&#xff0c;欢迎沟通交流。




推荐阅读
  • 本文介绍了Java并发库中的阻塞队列(BlockingQueue)及其典型应用场景。通过具体实例,展示了如何利用LinkedBlockingQueue实现线程间高效、安全的数据传递,并结合线程池和原子类优化性能。 ... [详细]
  • 作为一名新手,您可能会在初次尝试使用Eclipse进行Struts开发时遇到一些挑战。本文将为您提供详细的指导和解决方案,帮助您克服常见的配置和操作难题。 ... [详细]
  • 本文探讨了 Spring Boot 应用程序在不同配置下支持的最大并发连接数,重点分析了内置服务器(如 Tomcat、Jetty 和 Undertow)的默认设置及其对性能的影响。 ... [详细]
  • 本文详细探讨了HTML表单中GET和POST请求的区别,包括它们的工作原理、数据传输方式、安全性及适用场景。同时,通过实例展示了如何在Servlet中处理这两种请求。 ... [详细]
  • 全面解析运维监控:白盒与黑盒监控及四大黄金指标
    本文深入探讨了白盒和黑盒监控的概念,以及它们在系统监控中的应用。通过详细分析基础监控和业务监控的不同采集方法,结合四个黄金指标的解读,帮助读者更好地理解和实施有效的监控策略。 ... [详细]
  • Docker的安全基准
    nsitionalENhttp:www.w3.orgTRxhtml1DTDxhtml1-transitional.dtd ... [详细]
  • 本文详细分析了Hive在启动过程中遇到的权限拒绝错误,并提供了多种解决方案,包括调整文件权限、用户组设置以及环境变量配置等。 ... [详细]
  • 本文详细介绍了macOS系统的核心组件,包括如何管理其安全特性——系统完整性保护(SIP),并探讨了不同版本的更新亮点。对于使用macOS系统的用户来说,了解这些信息有助于更好地管理和优化系统性能。 ... [详细]
  • 在维护公司项目时,发现按下手机的某个物理按键后会激活相应的服务,并在屏幕上模拟点击特定坐标点。本文详细介绍了如何使用ADB Shell Input命令来模拟各种输入事件,包括滑动、按键和点击等。 ... [详细]
  • 基于KVM的SRIOV直通配置及性能测试
    SRIOV介绍、VF直通配置,以及包转发率性能测试小慢哥的原创文章,欢迎转载目录?1.SRIOV介绍?2.环境说明?3.开启SRIOV?4.生成VF?5.VF ... [详细]
  • dotnet 通过 Elmish.WPF 使用 F# 编写 WPF 应用
    本文来安利大家一个有趣而且强大的库,通过F#和C#混合编程编写WPF应用,可以在WPF中使用到F#强大的数据处理能力在GitHub上完全开源Elmis ... [详细]
  • 探讨如何真正掌握Java EE,包括所需技能、工具和实践经验。资深软件教学总监李刚分享了对毕业生简历中常见问题的看法,并提供了详尽的标准。 ... [详细]
  • 作者:守望者1028链接:https:www.nowcoder.comdiscuss55353来源:牛客网面试高频题:校招过程中参考过牛客诸位大佬的面经,但是具体哪一块是参考谁的我 ... [详细]
  • 深入解析TCP/IP五层协议
    本文详细介绍了TCP/IP五层协议模型,包括物理层、数据链路层、网络层、传输层和应用层。每层的功能及其相互关系将被逐一解释,帮助读者理解互联网通信的原理。此外,还特别讨论了UDP和TCP协议的特点以及三次握手、四次挥手的过程。 ... [详细]
  • 中科院学位论文排版指南
    随着毕业季的到来,许多即将毕业的学生开始撰写学位论文。本文介绍了使用LaTeX排版学位论文的方法,特别是针对中国科学院大学研究生学位论文撰写规范指导意见的最新要求。LaTeX以其精确的控制和美观的排版效果成为许多学者的首选。 ... [详细]
author-avatar
kingkongkoil
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有