我们经常看到八股文教你怎么估并发能力,但为什么可以这么估?
什么机制保证的
它们,如何知道IO,并作出资源让步
它们是谁
- 操作系统和JVM
计算机组成原理还在追我
在早期操作系统都是以 进程 为独立运行的基本单位,直到后面,计算机科学家又提出了更小的能独立运行的基本单位,它就是线程。
在现代操作系统,进程是最小的资源分配单位,线程是最小的运行单位,一个进程下面能有一个或多个线程,每个线程都有独立一套的寄存器和栈,这样可以确保线程的控制流是相对独立的。
进程>线程>协程
-
是谁做出的资源让步
- 是操作系统线程
-
何时让步?
- 操作系统内核执行某些操作就会阻塞,让步资源。
- 比如IO、网络、其它(我又不专业)
-
协程又是什么?当年计组没这东西啊?
- 重量级的Thread引发的必然优化
必须先明确JVM的地位
溯及JAVA的根本:多平台兼容、高级封装、JRE/JDK屏蔽操作系统差异提供标准能力
JVM基于此运行,而其中就会有一些native方法、和操作系统内核交互的方法,这些方法在JRE里对于不同操作系统有不同的实现。 - - 随便扯一嘴,正因此,即使容器化了,Java依旧主流,因为容器系统镜像间亦有差异。
Thread(JAVA中的)正是其一体现,JVM提供的是Thread本质是对操作系统能力的调用。
private native void start0();
重量级的“Thread”
不说官话,Thread几乎具备进程的全部特性,是在进程概念下的虚拟抽象,某种“时分复用”理念的产物。一堆Thread实际在同一个进程内,共享进程的全部,抢占时间。
既然是虚拟的不是硬件层面的,也就可以导出不能无限多线程的结论,随着线程数变多,协调这些线程的代价会越来越高。
那么,对于操作系统来说,创建一个Thread就是重量级的操作。涉及到操作系统的东西就没轻的。
另外线程间,虽然共享进程的资源,但不代表切换的代价就轻了,更底层的执行状态上下文保存依旧有代价。
(轻是相对进程而言的,那我们创建的线程肯定轻多了)
可以很自然的联想到,线程池的产生合理性。
Java的Thread存在的易混淆
-
线程状态混淆
-
JVM只是个套壳容器,他提供的线程状态,也基于JVM的理念,是让我们在屏蔽底层的基础上做开发的,所以并不和操作系统线程完全一致。
- 对于操作系统,会关心真实的线程状态是阻塞还是运行还是就绪
- 对于JVM而言,只关心这个线程在没在“运行”,即RUNNABLE
-
JVM不关心你是不是在等待什么IO资源阻塞了,对于JVM而言,这都叫“运行”。具体的真实物理层面的阻塞还是运行,那是操作系统的事。
-
所以我们会看到明明在IO阻塞,Jstack却显示RUNNABLE
-
必然的优化-Virtual Thread
线程是重量级的了,有数。
虽然线程已经可以达到说在阻塞IO时让出CPU了,但如果高密度IO操作的话,线程因为其重量级特色,我们无法拥有足够大数量的线程。极端下就会出现100个线程都在等io,我其实想4000个并发io你懂吧
虽然磁盘本质是同步IO的,但是可能出现磁盘提供服务的能力,高于N个线程来回切的基础上,对IO的需求。是线程切换的代价导致的吃不满io能力
所以又有了协程的概念,在线程之下又虚拟出一层“协程”,这次不再是操作系统级的了,而是各语言自己维护管理,在一个线程下管理数个协程,“代理”某些会引起线程阻塞的IO操作,做好钩子回调,在语言这一层又一次(奇怪,我为什么要说又)完成了X程状态的切换。
由操作系统及的重量级切换上下文代价,再次减负,减到仅由程序编写者自行根据业务诉求控制,设置锁等代价。
此事在JDK19抢鲜体验中亦有记载
结论,为什么JDK8中我们可以根据IO占比,估算并发能力
- JVM中的Java Thread不需要主动通知或做什么处理,本质上是操作系统的Thread在执行代码逻辑,你的逻辑触及操作系统提供的其他能力(IO等),自然操作系统就阻塞并让步资源了。
- 所以我们才可以根据发生的IO占时间比,去估算多线程跑满,X个核心能支持多少并发。
可以学到什么
*** 的封装屏蔽思想无处不在
专注于自己的领域,负责
你可以说,JDK8下JAVA确实不适合大规模IO计算此类场景。缺少协程特性,仅靠线程吃不满IO/内存能力