java与多线程

由于处理器的运行速度与内存的运行速度相差好几个数量级,所以引入了一层或几层的尽量接近处理器速度的高速缓存来解决问题,引入缓存又带来了缓存一致性的问题

以下为共享内存多核系统的设计,即常见操作系统

java内存模型

可以对比上图操作系统的设计,发现它与java的内存模型的相似度,因为解决的是同一类问题

进程,线程和协程

从通用应用程序的角度如何实现多线程

  1. 用内核线程(操作系统内核直接支持的线程)的高级接口轻量化进程(跟内核线程1:1映射),这也是我们常说的线程来实现

  2. 由用户线程实现,用户线程一般为程序自己对多线程的实现,跟内核线程为n:1映射

    用户线程的优势在于不需要系统内核支援,劣势也在于没有系统内核的支援,所有的线程操作都需要由用户程序自己去处理。线程的创建、销毁、切换和调度都是用户必须考虑的问题,而且由于操作系统只把处理器资源分配到进程,那诸如“阻塞如何处理”“多处理器系统中如何将线程映射到其他处理器上”这类问题解决起来将会异常困难,甚至有些是不可能实现的。因为使用用户线程实现的程序通常都比较复杂[1],除了有明确的需求外(譬如以前在不支持多线程的操作系统中的多线程程序、需要支持大规模线程数量的应用),一般的应用程序都不倾向使用用户线程。Java、Ruby等语言都曾经使用过用户线程,最终又都放弃了使用它。但是近年来许多新的、以高并发为卖点的编程语言又普遍支持了用户线程,譬如Golang、Erlang等,使得用户线程的使用率有所回升。
  3. 线程除了依赖内核线程实现和完全由用户程序自己实现之外,还有一种将内核线程与用户线程一起使用的实现方式,被称为N:M实现。在这种混合实现下,既存在用户线程,也存在轻量级进程。用户线程还是完全建立在用户空间中,因此用户线程的创建、切换、析构等操作依然廉价,并且可以支持大规模的用户线程并发。

Java和协程

java现在就是用的内核线程1:1映射程序的线程方式实现的多线程,因为映射到了系统线程中,所以切换调度成本比较高,成本主要来自于用户态到核心态的切换成本,而两种状态切换的开销主要是响应中断,保护和恢复执行现场的成本。

处理器要去执行线程A的程序代码时,并不是仅有代码程序就能跑得起来,程序是数据与代码的组合体,代码执行时还必须要有上下文数据的支撑。而这里说的“上下文”,以程序员的角度来看,是方法调用过程中的各种局部的变量与资源;以线程的角度来看,是方法的调用栈中存储的各类信息;而以操作系统和硬件的角度来看,则是存储在内存、缓存和寄存器中的一个个具体数值。物理硬件的各种存储设备和寄存器是被操作系统内所有线程共享的资源,当中断发生,从线程A切换到线程B去执行之前,操作系统首先要把线程A的上下文数据妥善保管好,然后把寄存器、内存分页等恢复到线程B挂起时候的状态,这样线程B被重新激活后才能仿佛从来没有被挂起过。这种保护和恢复现场的工作,免不了涉及一系列数据在各种寄存器、缓存中的来回拷贝,当然不可能是一种轻量级的操作。

如果说内核线程的切换开销是来自于保护和恢复现场的成本,那如果改为采用用户线程,这部分开销就能够省略掉吗?答案是“不能”。但是,一旦把保护、恢复现场及调度的工作从操作系统交到程序员手上,那我们就可以打开脑洞,通过玩出很多新的花样来缩减这些开销。

到现在,操作系统基本都支持多线程,但是程序使用用户线程的方式实现多线程也并没有消失,由于一开始用户线程大多设计成协同式调度(线程调度分为抢占式调度和协同式调度),所以也被称为协程。

说个题外话,没错,就是go语言天天吹的那个,java早期也是这样实现的多线程,后面因为不适用当时环境放弃了,现在环境又变成更适合协程了(分布式大行其道),java2018年已立项(Loom项目)准备再加入对协程的实现,目前好像尚未公布上线时间。

今天对Web应用的服务要求,不论是在请求数量上还是在复杂度上,与十多年前相比已不可同日而语,这一方面是源于业务量的增长,另一方面来自于为了应对业务复杂化而不断进行的服务细分。现代B/S系统中一次对外部业务请求的响应,往往需要分布在不同机器上的大量服务共同协作来实现,这种服务细分的架构在减少单个服务复杂度、增加复用性的同时,也不可避免地增加了服务的数量,缩短了留给每个服务的响应时间。这要求每一个服务都必须在极短的时间内完成计算,这样组合多个服务的总耗时才不会太长;也要求每一个服务提供者都要能同时处理数量更庞大的请求,这样才不会出现请求由于某个服务被阻塞而出现等待。Java目前的并发编程机制就与上述架构趋势产生了一些矛盾,1:1的内核线程模型是如今Java虚拟机线程实现的主流选择,但是这种映射到操作系统上的线程天然的缺陷是切换、调度成本高昂,系统能容纳的线程数量也很有限。以前处理一个请求可以允许花费很长时间在单体应用中,具有这种线程切换的成本也是无伤大雅的,但现在在每个请求本身的执行时间变得很短、数量变得很多的前提下,用户线程切换的开销甚至可能会接近用于计算本身的开销,这就会造成严重的浪费。传统的JavaWeb服务器的线程池的容量通常在几十个到两百之间,当程序员把数以百万计的请求往线程池里面灌时,系统即使能处理得过来,但其中的切换损耗也是相当可观的。

协程的主要优势是轻量,比传统内核线程轻量得多。HotSpot中线程栈的默认容量为1M,此外内核数据结构还会额外消耗16KB内存。而一个协程的栈通常在几百个字节到几kb之间,所以线程池有几百个就已经很大了,而协程多的可以数以十万计相差了千倍级。

以上观点基本来自于深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)周志明第五部分,本人不过是拾人牙慧,为了我自己更好的学习效果才写的。

  • 6
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值