资源描述
目录4 4517171844668691919297106109看清趋势,找准目标109看清趋势,找准目标Java 正青春:现状与技术趋势报告必备干货,所向披靡必备干货,所向披靡如何回答性能优化的问题,才能打动阿里面试官?10 问 10 答:你真的了解线程池吗?那些你不知道的 TCP 冷门知识!如何准备阿里技术面试?终面官现身说法!一手面经,稳拿 Offer一手面经,稳拿 Offer我是一名应届生,我觉得拿到心仪的 offer 不难十年前,他如何自学技术进阿里?为求职阿里我准备了 4 年,本科生 offer 经验分享!大佬指路,助你成功大佬指路,助你成功阿里研究员毕玄:又是一年校招季,我是这样考察学生的110看清趋势,找准目标5Java 正青春:现状与技术趋势报告Java 正青春:现状与技术趋势报告作者:好好学习简介:本文将从 JavaSE 开源现状、OpenJDK 版本生态到 OpenJDK 技术趋势三个方面讲述当前基础 Java 技术的现状,进一步讨论在云原生、AI、多语言生态领域支撑 Java 应用的基石Java Virtual Machine(JVM)技术,面向未来的演进趋势。一、一、背景背景1991 年,James Gosling 带领团队开始了一个叫Oak的项目,这个就是 Java的前身。1995 年,Java1.0 发布。“Write once,run anywhere这句 Java 口号想必大家耳熟能详。Java 刚开始出现的时候主要面向 Interactive Television 领域,直至后来几年的发展,当时的 SUN(后来在 2010 年被 Oracle 收购)一度想用 Java 来打造桌面的网络操作系统,取代当时如日中天的 Windows。不过 Java 后来的发展,不曾想虽未在桌面领域内取得多大的建树,出乎意料地,却在企业级应用领域开花结果,占据了如今几乎统治的地位。失之東隅,却收之桑榆。二、二、JavaSEJavaSE 开源现状开源现状Sun 在 2006 年的 Java One 大会上,宣布 Java 技术开源,随后 2006 年底在GPL 协议下发布 HotSpot 以及 javac,这是 Java 发展中的里程碑事件。阿里巴巴最早在 2012 签署 OCA,并参与到了 OpenJDK 的开发。Java 正青春:现状与技术趋势报告Java 正青春:现状与技术趋势报告另外,除了 OpenJDK 这条主线,在最近的几年里,Java 基础技术的开源有愈演愈烈趋势:2017 年,IBM 将内部使用 20 多年之久的 J9 虚拟机开源,并贡献到Eclipse Foundation,而随后 2018 年,Oracle 开源 GraalVM 1.0,其核心包含用Java 写的 Just-in-Time compiler/Graal,SubstrateVM 以及支持多语言解释器的Truffle 框架。各个企业开源的主要动机,想通过开源构建并受益于一个更为强大的语言生态系统。云+开源结合在一起,使得普通开发者以较低的门槛获得一流工具(链)的使用和体验,任何一家企业都可以像任何大型组织一样,使用的相同技术(democratizing),这是开发者的黄金时代。三、三、JavaJava is is StillStill Free:Free:你该选择什么样的你该选择什么样的 JDKJDK?Java 仍然免费,但随着 OracleJDK License 变化开始转向收费,OpenJDK 会逐渐取代 OracleJDK 成为市场主流,这点也可以从 JVM 2020 生态报告中看出趋势:OracleJDK 从前一年的 70%的开发者选择使用率降到 2020 年的 34%。OracleJDK 收费,在客观上也加剧了 OpenJDK 生态的碎片化趋势,出现了包括Alibaba Dragonwell 在内的多个基于 OpenJDK 的可选实现。企业在选择使用那个 Java Vendor 的 JDK 版本时,几个方面的考虑因素可以参考:Java 正青春:现状与技术趋势报告Java 正青春:现状与技术趋势报告同时,Alibaba Dragonwell 作为 OpenJDK 的下游,每个发行版都会同步上游最新更新,包括安全更新,问题修复等,并经过阿里内部大规模的应用集群测试。在新技术 Adoption 方面,Alibaba Dragonwell 目前发布和维护了 Java 8,11 两个 LTS 版本,阿里 JVM 团队会根据实际业务状况,移植 Java11+的相关功能到Java 8 和 11 两个版本,这样 Alibaba Dragonwell 用户可以在不跟进 Java 12,13 等这些 FR 版本的情况下,提前享受这些功能带来的技术红利。四、四、OpenJDKOpenJDK 技术趋势技术趋势纵观 Java 技术 20 多年的发展,始终围绕着两大主题:Productivity 以及Performance。在很多情况下,Java 在设计上 Productivity 是优于 Performance 考虑的。Java 引入的 Garbage Collector 把程序员从复杂的内存管理中解脱出来,但在另一方面 Java 应用始终困扰于 GC 暂停时间的影响。Java 基于栈式虚拟机的中间字节码设计,很好地抽象了不同平台(Intel,ARM 等)的差异性,同时通过 Just-in-Time(JIT)编译技术,解决的 Java 应用 peak 性能,但在另一方面 JIT 不可避免引入了Warmup 的代价,正常情况下 Java 程序永远需要先 load class,解释执行,然后再到高度优化的代码执行。Java 正青春:现状与技术趋势报告Java 正青春:现状与技术趋势报告产可用 3。实际上,在 AJDK 11 上,阿里巴巴团队 JVM 团队已经做了大量 Java11+到 Java 11 的 ZGC 移植工作,以及相关问题修复,2019 年双 11 和阿里数据库团队一起,让数据库应用运行在 ZGC 上,100+GB Heap 情况下 GC 暂停时间可以保持在 10ms 以内,详细讨论参考4。(三三)GraalGraal用 Java 开发的新一代 Just-in-Time 编译技术,用来替代目前 HostSot JVM 的C1/C2 编译器,OpenJDK 上的 Ahead-of-Time(AOT)技术也是基于 Graal 编译器开发。(五五)LoomLoomOpenJDK 社区协程项目,对应于 AJDK 的 Wisp 2.0 实现,详细讨论可以参考5。五、五、进击的进击的 JavaJava:面向未来演进:面向未来演进2020,站在一个全新的节点上,本文也从三个大的方面 Cloud Native,AI,以及多语言生态三个方面展望下未来的发展,有些讨论本身是超越 Java 本身的。(一一)面向面向 CloudCloud NativeNative 的语言进化的语言进化云原生时代,软件的交付方式发生的根本性变化。以 Java 为例,在之前 Java 开发者交付的是应用本身,具体体现在以 jar,war 的形式交付,而云原生则是以Java 正青春:现状与技术趋势报告Java 正青春:现状与技术趋势报告的 Java 团队与 OS 团队合作的Checkpoint Restore Fast Start-up技术(AZul 在JVM 技术峰会 2019 上也提出过类似的想法)则是在更加底层的技术栈上解决 Java快速拉起问题。在 Java for Cloud Native 方向,我们也开展了相关研发工作。Java 是静态语言,但是包含了大量的动态特性,包括反射,Class Loading,Bytecode Instrument(BCI)等等,这些动态特性本质上都是违反 GraalVM/SVM 所要求的 Closed-WorldAssumption(CWA)原则,这也是导致传统跑在 JVM 的 Java 应用不容易在 SVM 编译运行的主要原因。阿里巴巴 JVM 团队对 AJDK 做了静态化裁剪,务求在 Java 静/动态特性之间找到一个确定的边界,从 JDK 的层面为 Java 静态编译提供可能性。同时向上,与蚂蚁中间团队合作,定义面向静态编译的 Java 编程模型,通过编程框架来约束-Java 应用的开发是面向静态编译友好的。我们静态编译了基于蚂蚁开源中间件SOFAStack 构建的服务注册中心 Meta 节点应用,相较于传统 的运行在 JVM 上,性能有量级的提升:服务启动时间降低了 17 倍,可执行文件大小降低了 3.4 倍,运行时内存降低了一半。详见6。(二二)AIAI 的兴起,编程语言异构计算的新挑战的兴起,编程语言异构计算的新挑战2005 年,时任 Intel CTO 的 Justin Rattner,说过“We are at the cusp ofa transition to multicore,multithreaded architectures”,在前后的十几年中,编程语言与编译器领域一直在努力面向 parallel architectural paradigm 做优化探索。随着 AI 这些年的兴起,不同的时间节点,相似的场景,面向 FPGA/GPU 异构计算场景,对编程语言与编译器领域提出了新的挑战。Java 正青春:现状与技术趋势报告Java 正青春:现状与技术趋势报告时间(单位:milliseconds)越短,性能越好(三三)PolyglotPolyglot ProgramingPrograming,链接多语言生态,链接多语言生态Polyglot Programming 并不是一个新的概念。在 Managed Runtime 领域,2017 年 IBM 开源 Open Managed Runtime(OMR),以及 2018 年 Oracle 开源Truffle/Graal 技术。OMR 和 Graal 技术让开发人员实现一个新的语言成本大幅下降。前者 OMR 以 C、C+组件的形式提供了 Garbage Collection(GC),Just-in-Time(JIT)以及 Reliability,availability and serviceability(RAS,工具)等,开发人员可以依赖这些组件,通过 glue 的方式基于这些组件实现自己的高性能语言。而后者Truffle/Graal,Truffle 是一个依赖 AST parser 实现新的语言的 Java 框架,本质上是将你的新的语言映射到 JVM 世界。不同于 Scala,JRuby 这些围绕 JVM 生态本身构建的语言,他们本质是还是 Java,无论是 OMR,还是 Truffle/Graal,他们都提供了生产级的 GC,JIT,以及 RAS 服务支持,新开发的语言完全不需要再重新实现这些底层技术。Java 正青春:现状与技术趋势报告16从业界来看,面向特定领域的 Domain Specific Language(DSL)语言已经有向这些技术迁移的趋势,高盛正在与 Graal 社区合作,把他们的 DSL 迁移到 Graal 上。另外 Ruby/OMR,Python/Graal,JS/Graal,WASM/Graal 等这些真正链接不同语言生态的项目,也正在迅速发展起来。回到 AJDK,Graal 已经在 AJDK 8 开始支持,JS/Graal 这样成熟的技术,已经在阿里内部业务上线。六、六、最后最后Java 是一项二十多年前被发明出来的技术,她历经磨难,几易其主,但却历久弥新。这篇报告旨在为 Java 的开发者们梳理下目前的 Java 技术现状,以及讨论在云,AI 等这些重要领域内 Java 技术的演进趋势。在介绍的相关部分,我们也穿插了阿里的一些工程实践。作为世界上最大的 Java 用户之一,我们也一直在探索把前沿的 Java 技术,通过在阿里丰富的业务场景的试验,真正把这些技术应用于真实的生产环境。我们也非常乐于分享和贡献 Java 领域的经验、实践与技术洞见,共同促进 Java 的发展。七、七、参考参考https:/ Java 应用中的相关概念。如何回答性能优化的问题,才能打动阿里面试官?如何回答性能优化的问题,才能打动阿里面试官?上面我们阐述了进行性能优化的必要性。假设现在我们的应用已经有了性能问题(eg.CPU 水位比较高),准备开始进行优化工作了,在这个过程中,潜在的痛点会有哪些呢?下面列出一些较为常见的:对性能优化的流程不是很清晰。初步定为一个疑似瓶颈点后,就兴高采烈地吭哧吭哧开始干,最终解决的问题其实只是一个浅层次的性能瓶颈,真实的问题的根源并未触达;对性能瓶颈点的分析思路不是很清晰。CPU、网络、内存.这么多的性能指标,我到底该关注什么,应该从哪一块儿开始入手?对性能优化的工具不了解。遇到问题后,不清楚该用哪个工具,不知道通过工具得到的指标代表什么。二、二、性能优化的流程性能优化的流程在性能优化这个领域,并没有一个严格的流程定义,但是对于绝大多数的优化场景,我们可以将其过程抽象为下面四个步骤。准备阶段:主要工作是是通过性能测试,了解应用的概况、瓶颈的大概方向,明确优化目标;分析阶段:通过各种工具或手段,初步定位性能瓶颈点;调优阶段:根据定位到的瓶颈点,进行应用性能调优;测试阶段:让调优过的应用进行性能测试,与准备阶段的各项指标进行对比,观测其是否符合预期,如果瓶颈点没有消除或者性能指标不符合预期,则重复步骤 2 和3。如何回答性能优化的问题,才能打动阿里面试官?如何回答性能优化的问题,才能打动阿里面试官?(一一)通用流程详解通用流程详解在上述通用流程的四个步骤当中,步骤 2 和 3 我们会在接下来两个部分重点进行介绍。首先我们来看一下,在准备阶段和测试阶段,我们需要做一些什么。准备阶段准备阶段准备阶段是非常关键的一步,不能省略。首先,需要对我们进行调优的对象进行详尽的了解,所谓知己知彼,百战不殆。对性能问题进行粗略评估,过滤一些因为低级的业务逻辑导致的性能问题。譬如,线上应用日志级别不合理,可能会在大流量时导致 CPU 和磁盘的负载飙高,这种情况调整日志级别即可;了解应用的的总体架构,比如应用的外部依赖和核心接口有哪些,使用了哪些组件和框架,哪些接口、模块的使用率较高,上下游的数据链路是怎么样的等;了解应用对应的服务器信息,如服务器所在的集群信息、服务器的 CPU/内存信息、安装的 Linux 版本信息、服务器是容器还是虚拟机、所在宿主机混部后是否对当前应用有干扰等;其次,我们需要获取基准数据,然后结合基准数据和当前的一些业务指标,确定此次性能优化的最终目标。如何回答性能优化的问题,才能打动阿里面试官?如何回答性能优化的问题,才能打动阿里面试官?(二二)注意事项注意事项在进行性能优化时,了解下面这些注意事项可以让我们少走一些弯路。性能瓶颈点通常呈现 2/8 分布,即 80%的性能问题通常是由 20%的性能瓶颈点导致的,2/8 原则也意味着并不是所有的性能问题都值得去优化;性能优化是一个渐进、迭代的过程,需要逐步、动态地进行。记录基准后,每次改变一个变量,引入多个变量会给我们的观测、优化过程造成干扰;不要过度追求应用的单机性能,如果单机表现良好,则应该从系统架构的角度去思考;不要过度追求单一维度上的极致优化,如过度追求 CPU 的性能而忽略了内存方面的瓶颈;如何回答性能优化的问题,才能打动阿里面试官?如何回答性能优化的问题,才能打动阿里面试官?图片来源:http:/ Linux 系统资源的角度去观测性能指标的,这要求我们对 Linux 各个子系统的功能、原理要有所了解。举例:遇到性能问题了,我们不会拿每个子系统下的工具都去试一遍,大多数情况是:我们怀疑某个子系统有问题,然后根据这张图上列举的工具,去观测或者验证我们的猜想,这无疑拔高了对性能优化经验的要求;如何回答性能优化的问题,才能打动阿里面试官?如何回答性能优化的问题,才能打动阿里面试官?如何回答性能优化的问题,才能打动阿里面试官?如何回答性能优化的问题,才能打动阿里面试官?(一一)CPU&CPU&线程线程和 CPU 相关的指标主要有以下几个。常用的工具有 top、ps、uptime、vmstat、pidstat 等。CPU 利用率(CPU Utilization)CPU 平均负载(Load Average)上下文切换次数(Context Switch)top-12:20:57 up 25 days,20:49,2 users,load average:0.93,0.97,0.79Tasks:51 total,1 running,50 sleeping,0 stopped,0 zombie%Cpu(s):1.6 us,1.8 sy,0.0 ni,89.1 id,0.1 wa,0.0 hi,0.1 si,7.3 stKiB Mem:8388608 total,476436 free,5903224 used,2008948 buff/cacheKiB Swap:0 total,0 free,0 used.0 avail MemPID USER PR NI VIRT RES SHR S%CPU%MEM TIME+COMMAND119680 admin 20 0 600908 72332 5768 S 2.3 0.9 52:32.61 obproxy65877 root 20 0 93528 4936 2328 S 1.3 0.1 449:03.61 alisentry_cli第一行显示的内容:当前时间、系统运行时间以及正在登录用户数。load average后的三个数字,依次表示过去 1 分钟、5 分钟、15 分钟的平均负载(Load Average)。平均负载是指单位时间内,系统处于可运行状态(正在使用 CPU 或者正在等待 CPU的进程,R 状态)和不可中断状态(D 状态)的平均进程数,也就是平均活跃进程数,CPU 平均负载和 CPU 使用率并没有直接关系。如何回答性能优化的问题,才能打动阿里面试官?如何回答性能优化的问题,才能打动阿里面试官?和内核态的 CPU 利用率。CPUCPU 相关指标异常的分析思路是什么?相关指标异常的分析思路是什么?CPU 利用率:如果我们观察某段时间系统或应用进程的 CPU 利用率一直很高(单个 core 超过 80%),那么就值得我们警惕了。我们可以多次使用 jstack 命令 dump 应用线程栈查看热点代码,非 Java 应用可以直接使用 perf 进行 CPU 采采样,离线分析采样数据后得到 CPU 执行热点(Java 应用需要符号表进行堆栈信息映射,不能直接使用 perf 得到结果)。如何回答性能优化的问题,才能打动阿里面试官?如何回答性能优化的问题,才能打动阿里面试官?线程的状态是否异常。观察 WAITING/BLOCKED 线程是否过多(线程数设置过多或锁竞争剧烈),结合应用内部锁使用的情况综合分析;结合 CPU 利用率,观察是否存在大量消耗 CPU 的线程。(二二)内存内存&堆堆和内存相关的指标主要有以下几个,常用的分析工具有:top、free、vmstat、pidstat 以及 JDK 自带的一些工具。系统内存的使用情况,包括剩余内存、已用内存、可用内存、缓存/缓冲区;进程(含 Java 进程)的虚拟内存、常驻内存、共享内存;进程的缺页异常数,包含主缺页异常和次缺页异常;Swap 换入和换出的内存大小、Swap 参数配置;如何回答性能优化的问题,才能打动阿里面试官?如何回答性能优化的问题,才能打动阿里面试官?buff/cache 是缓存和缓冲区的大小。缓存(cache):是从磁盘读取的文件的或者向磁盘写文件时的临时存储数据,面向文件。使用 cachestat 可以查看整个系统缓存的读写命中情况,使用 cachetop 可以观察每个进程缓存的读写命中情况。缓冲区(buffer)是写入磁盘数据或从磁盘直接读取的数据的临时存储,面向块设备。free 命令的输出中,这两个指标是加在一起的,使用 vmstat 命令可以区分缓存和缓冲区,还可以看到 Swap 分区换入和换出的内存大小。了解到常见的内存指标后,常见的内存问题又有哪些?总结如下:系统剩余内存/可用不足(某个进程占用太多、系统本身内存不足),内存溢出;内存回收异常:内存泄漏(进程在一段时间内内存使用持续走高)、GC 频率异常;缓存使用过大(大文件读取或写入)、缓存命中率不高;缺页异常过多(频繁的 I/O 读);Swap 分区使用异常(使用过大);内存相关指标异常后,分析思路是怎么样的?内存相关指标异常后,分析思路是怎么样的?使用 free/top 查看内存的全局使用情况,如系统内存的使用、Swap 分区内存使用、缓存/缓冲区占用情况等,初步判断内存问题存在的方向:进程内存、缓存/缓冲区、Swap 分区;观察一段时间内存的使用趋势。如通过 vmstat 观察内存使用是否一直在增长;通过 jmap 定时统计对象内存分布情况,判断是否存在内存泄漏,通过 cachetop命令,定位缓冲区升高的根源等;根据内存问题的类型,结合应用本身,进行详细分析。如何回答性能优化的问题,才能打动阿里面试官?使用 vmstat 或者 sar 观察一下各个进程内存使用变化趋势-发现某个进程的内存时候用持续走高-如果是 Java 应用,可以使用 jmap/VisualVM/heap dump分析等工具观察对象内存的分配,或者通过 jstat 观察 GC 后的应用内存变化-结合业务场景,定位为内存泄漏/GC 参数配置不合理/业务代码异常等。(三三)磁盘磁盘&文件文件在分析和磁盘相关的问题时,通常是将其和文件系统同时考虑的,下面不再区分。和磁盘/文件系统相关的指标主要有以下几个,常用的观测工具为 iostat 和 pidstat,前者适用于整个系统,后者可观察具体进程的 I/O。磁盘 I/O 利用率:是指磁盘处理 I/O 的时间百分比;磁盘吞吐量:是指每秒的 I/O 请求大小,单位为 KB;I/O 响应时间,是指 I/O 请求从发出到收到响应的间隔,包含在队列中的等待时间和实际处理时间;IOPS(Input/Output Per Second):每秒的 I/O 请求数;I/O 等待队列大小,指的是平均 I/O 队列长度,队列长度越短越好;使用 iostat 的输出界面如下:$iostat-dxLinux 3.10.0-327.ali2010.alios7.x86_64(loginhost2.alipay.em14)10/20/2019 x86_64(32 CPU)39如何回答性能优化的问题,才能打动阿里面试官?Device:rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm%utilsda 0.01 15.49 0.05 8.21 3.10 240.49 58.92 0.04 4.38 2.39 4.39 0.09 0.07上图中%util,即为磁盘 I/O 利用率,同 CPU 利用率一样,这个值也可能超过100%(存在并行 I/O);rkB/s 和 wkB/s 分别表示每秒从磁盘读取和写入的数据量,即吞吐量,单位为 KB;磁盘 I/O 处理时间的指标为 r_await 和 w_await 分别表示读/写请求处理完成的响应时间,svctm 表示处理 I/O 所需要的平均时间,该指标已被废弃,无实际意义。r/s+w/s 为 IOPS 指标,分别表示每秒发送给磁盘的读请求数和写请求数;aqu-sz 表示等待队列的长度。pidstat 的输出大部分和 iostat 类似,区别在于它可以实时查看每个进程的 I/O情况。如何判断磁盘的指标出现了异常?如何判断磁盘的指标出现了异常?当磁盘 I/O 利用率长时间超过 80%,或者响应时间过大(对于 SSD,从 0.0 x 毫秒到 1.x 毫秒不等,机械磁盘一般为 5ms10ms),通常意味着磁盘 I/O 存在性能瓶颈;如果%util 很大,而 rkB/s 和 wkB/s 很小,一般是因为存在较多的磁盘随机读写,最好把随机读写优化成顺序读写,(可以通过 strace 或者 blktrace 观察 I/O 是否连续判断是否是顺序的读写行为,随机读写应可关注 IOPS 指标,顺序读写可关注吞吐量指标);如何回答性能优化的问题,才能打动阿里面试官?如何回答性能优化的问题,才能打动阿里面试官?一般来说,应用层的网络瓶颈有如下几类:集群或机器所在的机房的网络带宽饱和,影响应用 QPS/TPS 的提升;网络吞吐出现异常,如接口存在大量的数据传输,造成带宽占用过高;网络连接出现异常或错误;网络出现分区。带宽和网络吞吐这两个指标,一般我们会关注整个应用的,通过监控系统可直接得到,如果一段时间内出现了明显的指标上升,说明存在网络性能瓶颈。对于单机,可以使用 sar 得到网络接口、进程的网络吞吐。使用 ping 或者 hping3 可以得到是否出现网络分区、网络具体时延。对于应用,我们更关注整个链路的时延,可以通过中间件埋点后输出的 trace 日志得到链路上各个环节的时延信息。使用 netstat、ss 和 sar 可以获取网络连接数或网络错误数。过多网络链接造成的开销是很大的,一是会占用文件描述符,二是会占用缓存,因此系统可以支撑的网络链接数是有限的。(五五)工具总结工具总结可以看到的是,在分析 CPU、内存、磁盘等的性能指标时,有几种工具是高频出现的,如 top、vmstat、pidstat,这里稍微总结一下:如何回答性能优化的问题,才能打动阿里面试官?如何回答性能优化的问题,才能打动阿里面试官?需要注意的是,性能工具只是解决性能问题的手段,我们了解常用工具的一般用法即可,不要在工具学习上投入过多精力。在通过工具得到异常指标,初步定位瓶颈点后,如何进一步进行确认和调优?这里将给出常见的一些调优分析思路,内容会按照 CPU、内存、网络、磁盘等进行组织。详情见:https:/ 问 10 答:你真的了解线程池吗?10 问 10 答:你真的了解线程池吗?我在查找资料的过程中,发现有些问题存在争议。后面发现,一部分原因是因为不同 JDK 版本的现实是有差异的。因此,下面的分析是基于当下最常用的版本 JDK1.8,并且对于存在争议的问题,我们分析源码,源码才是最准确的。一、一、corePoolSize=0corePoolSize=0 会怎么样会怎么样这是一个争议点。我发现大部分博文,不论是国内的还是国外的,都是这样回答这个问题的:提交任务后,先判断当前池中线程数是否小于 corePoolSize,如果小于,则创建新线程执行这个任务。否者,判断等待队列是否已满,如果没有满,则添加到等待队列。否者,判断当前池中线程数是否大于 maximumPoolSize,如果大于则拒绝。否者,创建一个新的线程执行这个任务。按照上面的描述,如果 corePoolSize=0,则会判断等待队列的容量,如果还有容量,则排队,并且不会创建新的线程。但其实,这是老版本的实现方式,从 1.6 之后,实现方式就变了。我们直接看execute 的源码(submit 也依赖它),我备注出了关键一行:10 问 10 答:你真的了解线程池吗?46int c=ctl.get();if(workerCountOf(c)10 问 10 答:你真的了解线程池吗?答答:上述问题需区分 JDK 版本。在 1.6 版本之后,如果 corePoolSize=0,提交任务时如果线程池为空,则会立即创建一个线程来执行任务(先排队再获取);如果提交任务的时候,线程池不为空,则先在等待队列中排队,只有队列满了才会创建新线程。所以,优化在于,在队列没有满的这段时间内,会有一个线程在消费提交的任务;1.6 之前的实现是,必须等队列满了之后,才开始消费。二、二、线程池创建之后,会立即创建核心线程么线程池创建之后,会立即创建核心线程么之前有人问过我这个问题,因为他发现应用中有些 Bean 创建了线程池,但是这个Bean 一般情况下用不到,所以咨询我是否需要把这个线程池注释掉,以减少应用运行时的线程数(该应用运行时线程过多。)答答:不会。从上面的源码可以看出,在刚刚创建 ThreadPoolExecutor 的时候,线程并不会立即启动,而是要等到有任务提交时才会启动,除非调用了 prestartCoreThread/prestartAllCoreThreads 事先启动核心线程。prestartCoreThread:Starts a core thread,causing it to idly wait for work.This overrides the default policy of starting core threads only when new tasks are executed.10 问 10 答:你真的了解线程池吗?1:在一般情况下就开始使用多线程(corePoolSize 个),当并发请求特别多,等待队列都满了之后,继续加大线程数。但是当请求没有的时候,允许核心线程也终止。所以 corePoolSize=0 的效果,基本等同于 allowsCoreThreadTimeOut=true&corePoolSize=1,但实现细节其实不同。4910 问 10 答:你真的了解线程池吗?答答:在 JDK1.6 之后,如果 allowsCoreThreadTimeOut=true,核心线程也可以被终止。四、四、如何保证线程不被销毁如何保证线程不被销毁首先我们要明确一下线程池模型。线程池有个内部类 Worker,它实现了 Runnable接口,首先,它自己要 run 起来。然后它会在合适的时候获取我们提交的 Runnable 任务,然后调用任务的 run()接口。一个 Worker 不终止的话可以不断执行任务。我们前面说的“线程池中的线程”,其实就是 Worker;等待队列中的元素,是我们提交的 Runnable 任务。每一个 Worker 在创建出来的时候,会调用它本身的 run()方法,实现是 runWorker(this),这个实现的核心是一个 while 循环,这个循环不结束,Worker 线程就不会终止,就是这个基本逻辑。在这个 while 条件中,有个 getTask()方法是核心中的核心,它所做的事情就是从等待队列中取出任务来执行:如果没有达到 corePoolSize,则创建的 Worker 在执行完它承接的任务后,会用workQueue.take()取任务、注意,这个接口是阻塞接口,如果取不到任务,Worker线程一直阻塞。10 问 10 答:你真的了解线程池吗?10 问 10 答:你真的了解线程池吗?task.run();catch(RuntimeException x)thrown=x;throw x;catch(Error x)thrown=x;throw x;catch(Throwable x)thrown=x;throw new Error(x);finally afterExecute(task,thrown);finally task=null;pletedTasks+;w.unlock();completedAbruptly=false;finally processWorkerExit(w,completedAbruptly);private Runnable getTask()boolean timedOut=false;/Did the last poll()time out?for(;)int c=ctl.get();int rs=runStateOf(c);10 问 10 答:你真的了解线程池吗?=SHUTDOWN&(rs=STOP|workQueue.isEmpty()decrementWorkerCount();return null;int wc=workerCountOf(c);/Are workers subject to culling?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;5310 问 10 答:你真的了解线程池吗?答答:实现方式非常巧妙,核心线程(Worker)即使一直空闲也不终止,是通过workQueue.take()实现的,它会一直阻塞到从等待队列中取到新的任务。非核心线程空闲指定时间后终止是通过 workQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS)实现的,一个空闲的 Worker 只等待 keepAliveTime,如果还没有取到任务则循环终止,线程也就运行结束了。引申思考引申思考Worker 本身就是个线程,它再调用我们传入的 Runnable.run(),会启动一个子线程么?如果你还没有答案,再回想一下 Runnable 和 Thread 的关系。五、五、空闲线程过多会有什么问题空闲线程过多会有什么问题笼统地回答是会占用内存,我们分析一下占用了哪些内存。首先,比较普通的一部分,一个线程的内存模型:虚拟机栈本地方法栈程序计数器10 问 10 答:你真的了解线程池吗?10 问 10 答:你真的了解线程池吗?答答:在 JDK1.8 中,keepAliveTime=0 表示非核心线程执行完立刻终止。默认情况下,keepAliveTime 小于 0,初始化的时候才会报错;但如果 allowsCoreThreadTimeOut,keepAliveTime 必须大于 0,不然初始化报错。七、七、怎么进行异常处理怎么进行异常处理很多代码的写法,我们都习惯按照常见范式去编写,而没有去思考为什么。比如:如果我们使用 execute()提交任务,我们一般要在 Runable 任务的代码加上try-catch 进行异常处理。如果我们使用 submit()提交任务,我们一般要在主线程中,对 Future.get()进行 try-catch 进行异常处理。但是在上面,我提到过,submit()底层实现依赖 execute(),两者应该统一呀,为什么有差异呢?下面再扒一扒 submit()的源码,它的实现蛮有意思。首先,ThreadPoolExecutor 中没有 submit 的代码,而是在它的父类 AbstractExecutorService 中,有三个 submit 的重载方法,代码非常简单,关键代码就两行:10 问 10 答:你真的了解线程池吗?56public Future submit(Runnable task)if(task=null)throw new NullPointerException();RunnableFuture ftask=newTaskFor(task,null);execute(ftask);return ftask;public Future submit(Runnable task,T result)if(task=null)throw new NullPointerExce
展开阅读全文