java协程总结
线程协程介绍
Java线程在JDK1.2之前,是基于称为“绿色线程”(Green
Threads)的用户线程实现的,而在JDK1.2中,线程模型替换为基
于操作系统原生线程模型来实现。因此,在目前的JDK版本中,操
作系统支持怎样的线程模型,在很大程度上决定了Java虚拟机的线
程是怎样映射的,这点在不同的平台上没有办法达成一致,虚拟机
规范中也并未限定Java线程需要使用哪种线程模型来实现。线程模
型只对线程的并发规模和操作成本产生影响,对Java程序的编码和
运行过程来说,这些差异都是透明的。
也就说JDK1.2之前,程序员们为JVM开发了自己的一个线程调度内
核,而到操作系统层面就是用户空间内的线程实现。而到了JDK1.2
及以后,JVM选择了更加稳健且方便使用的操作系统原生的线程模
型,通过系统调用,将程序的线程交给了操作系统内核进行调度。
现在的Java中线程的本质,其实就是操作系统中的线程
Main线程是个非守护线程,不能设置成守护线程。
Main线程结束,其他线程一样可以正常运行。
Main线程结束,其他线程也可以立刻结束,当且仅当这些子线程都
是守护线程。
协程:协程是一种用户态的上下文切换技术,可以理解为是“用户
态”的多线程。协程是在一个线程执行过程中可以在一个子程序的
预定或者随机位置中断,然后转而执行别的子程序,在适当的时候
再返回来接着执行。他本身是一种特殊的子程序或者称作函数。
与进程、线程内的区别:线程包含于进程,协程包含于线程。只要
内存足够,一个线程中可以有任意多个协程,但某一时刻只能有一
个协程在运行,多个协程分享该线程分配到的计算机资源。协程和
多线程下的线程类似:有自己的堆栈,自己的局部变量,有自己的
指令指针,和其他协程程序共享全局变量等信息。单CPU同一时刻
只能执行一个协程。
阿里wisp2
原理
Wisp2中的一些线程:
WispSysmon:用来定时检查执行时间过长的协程(会进行抢占)
WispDaemon:在线程被转成协程后,维持原来的java的daemon语
义
WispEventPump:监听事件,一般是网络上的fd事件,在Linux下对
应一个epolleventloop
其它的概念:
•Carrier:代码的实际执行者,对应一个操作系统提供的线程,
同时包括调度所需的上下文信息(IO事件,定时事件),维护调
度所需数据结构
•WispEngine:一组Carrier组成的执行器,协程创建出来后会
绑定到一个engine,由engine下的carrier交替执行
•Scheduler:对应一个WispEngine,carrier之间的协作以及
steal策略都由scheduler实现
•WorkStealing:carrier之间互相窃取任务以平衡队列长度
•SysMonitor:SysMonitor负责监控每个Carrier上任务的执行
状态,在长时间未发生切换时抢占协程
•抢占:让长时间执行CPU代码的协程主动yield出CPU
•Coroutine:JKU提供的协程,主要提供了切换的能力
•WispTask:一个协程所需的调度结构,对应一个Coroutine。
一般是映射到一个Thread对象
•EventPump:事件源,一般是网络上的fd事件,在Linux下
对应一个epolleventloop
•全转模式:将所有线程转为协程
WispEngine下有一系列协程,以及执行协程的Carrier(线程);
通过调度器Scheduler进行调度,对事件就绪(epoll的callback)
的协程放入到carrier的队列当中,carrier进行队列扫描获取任务执
行,carrier直接可以相互窃取任务。
调度执行
carrier线程本身会执行不断从workQueue上拉取协程,并切换至
该协程中执行,此时carrier被这个协程占据。当协程执行过程中需
要挂起,会触发切换至carrier线程原先的上下文,继续触发调度。
当carrier发现自身队列全空时会考虑steal任务,帮其他carrier减
少压力。我们以一个读取jdbc的协程来举例:
1.协程读取jdbcdriver底层的Socket,此时数据包尚未收到,
协程需要等待,于是
i.将这个socket的读时间注册到eventPump
ii.切换到carrier的逻辑,让出控制流
r扫描队列,发现没有任务需要执行,将自己挂起,节
约资源
ump上的事件就绪,唤醒协程
i.将协程入队
ii.向os唤醒carrier
r从队列上拿到协程,继续读取socket,并执行后续的
逻辑
通过上述方式,协程分片使用Carrier的计算资源。
简单使用
使用方式和正常的线程操作一样。
staticfinalExecutorServiceTHREAD_POOL
=hedThreadPool();
publicstaticvoidmain(String[]args)throwsException{
BlockingQueue
LinkedBlockingQueue<>();
THREAD_(()->pingpong(q2,q1));//threadA
Future<?>f=THREAD_(()->pingpong(q1,q2));//threadB
((byte)1);
n(()+"ms");
}
privatestaticlongpingpong(BlockingQueue
out)throwsException{
longstart=tTimeMillis();
for(inti=0;i<1_000_000;i++)(());
tTimeMillis()-start;
}
存在的问题
1.由于兼容现有的线程模型,所以支持的协程量级较少
2.线程默认全部转协程(可以通过黑名单控制不转协程),对于复
杂系统需要对系统中所有线程有比较清晰的认识,部分线程不适合
转协程,比如forkjoin,转协程返回会丢失一部分性能
Quasar
原理
Quarar使用continuation的方式记录协程状态,通过Java
Agent或者在编译过程中修改主程序bytecode来实现。Quasar
中的协程叫fiber。Fiber可以被Quasar定义的scheduler调度,
一个continuation记录着运行实例的状态,而且会被随时中断,
并且也会随后在他被中断的地方恢复。Quasar其实是通过修改
bytecode来达到这个目的,所以运行Quasar程序的时候,你需
要先通过java-agent在运行时修改你的代码,当然也可以在编译
期间这么干。Quasar默认使用ForkJoinPool这个具有work-
stealing功能的线程池来当调度器。work-stealing非常重要,因
为你不清楚哪个Fiber会先执行完,而work-stealing可以动态的
从其他的等等队列偷一个context过来,这样可以最大化使用
CPU资源。
Quasar会通过java-agent在运行时扫描哪些方法是可以中断
的,同时会在方法被调用前和调度后的方法内插入一些
continuation逻辑,如果在方法上定义了@Suspendable注解,
那Quasar会对调用该注解的方法插入一些字节码,这些字节码
的逻辑就是记录当前Fiber栈上的状态,以便在未来可以动态的
恢复。
简单使用
newFiber
@Override
publicvoidrun()throwsSuspendExecution,InterruptedException{
newFiber
@Override
publicvoidrun()throwsSuspendExecution,InterruptedException{
(30);
}
}).start();
}
}).start();
存在的问题
最大的问题就是需要抛SuspendExecution异常,该异常是捕获异
常,但仅作为识别挂起方法的声明,在运行时不会实际抛出。使用
者必须逐层抛出该异常直至新建协程的一层。当方法内部存在
try/catch语句时,也必须抛出该异常。
对比
Wisp2VSQuasar:
1.使用成本
Wisp基于JVM栈操作、结合Runtime对阻塞方法的支持以及
加入调度器,来让应用无需改动地获得异步的性能,所以兼容原
有的编程方式,wisp2协程的使用方式和原有线程使用方式一
致。Wisp2内部将线程转换为wisp2协程。所以只需要替换
jdk,理论上无使用成本
Quasar引入了fiber的概念,firber的使用类似线程,只不过需
要抛SuspendExecution异常,有一定的使用成本。
2.协程容量
Wisp2要兼容原有的线程模型,所以官方反馈不会支持海量的协
程,实际测试协程超过1万响应会有些问题。
Quasar可以支持到百万级别的协程。
3.协程性能
Wisp2目前只能在jdk8上运行,在jdk8环境下测试,quasar性
能要好些。
本文发布于:2022-12-28 11:47:49,感谢您对本站的认可!
本文链接:http://www.wtabcd.cn/fanwen/fan/90/46453.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |