范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文
国学影视

JVM为什么需要有栈协程

  旧有的servlet生态的线程模型
  首先我们先要聊一聊现在我们用的最多的servlet的执行模型是什么:
  这个dispatch其实就是一个EventLoop或者说是一个selector来检测注册到其上的链接状态发生的变化
  以Tomcat为例子,当这个selector发现存在一个链接可读时,就会封装一个读取和后续处理的操作丢到worker线程中执行,在大部分情况下请求的读取和写出都是绑定到一个线程的,这里我们不讨论很细节的实现,只需要稍微理解一下线程模型即可。
  即我们可以发现HttpRequest的生命周期可以用ThreadLocal来代表,不会存在同一个线程交错处理多个请求的情况(排除servlet3.1引入的async-request api情况,这个我想大部分人也不太会使用)
  再结合我们经常使用的client的实现来思考,比如基于socket api的bio实现的jdbc,哪怕是本质是非阻塞也要封装出同步接口的lettuce或者okhttp3,这些client我们在使用时会阻塞住当前的线程。此时为了继续对外提供服务就需要继续加线程,就导致了一个普通的springboot服务有时候甚至会使用数百个内核线程在不停的切换,大量的内核线程带来了什么结果?内存占用高,大量的上下文切换导致的性能下降(cache miss之类的),高昂的锁代价,浪费的CPU时钟资源。
  我们只能这样做吗?显然不是,我们来看看其他的语言是怎么做的。Go,node.js之类的的兴起,让更多的开发者发现我们其实只需要少量的内核线程就可以支撑起原来上百线程的并发能力。事实证明,在web这种无状态的,IO用时较多的程序类型只要用少量的(n个cup核心数的线程数目)就可以达成我们的全部需要。  如何在jdk8的情况下弥补这一切?
  总结一下需求,我们需要一个框架可以当io未完毕时线程可以切换走执行其他的任务,等完毕后再执行后续的事情
  其实用少量线程支持大量并发的技术栈早已出现,甚至我们在自己部门的仓库里面也能看到这个技术——响应式技术栈,比如说Spring WebFlux,Vert.x,Quarkus
  从下图看vertx的综合benchmark非常的强
  以Vert.x为例子,他的代码风格是这样的 本质上就Future套Future,将异步操作串联在一起  private void addOrder(Router router){         router.post(prefix)                 .handler(AccessBaseSessionHandler.createLeastMode(Roles.USER))                 .handler(BodyHandler.create())                 .handler(ValidationJsonHandler.create(OrderVO.class))                 .handler(rc -> {                     LoginUserPO loginUserPO = rc.session().get(UserController.userKeyInSession);                     OrderVO orderVO = rc.get(ValidationJsonHandler.VALUE_KEY);                     orderService.addNewOrder(orderVO,loginUserPO.getUserId())                             .map(v -> ResponseEntity.success(orderVO.getOrderId(),200).toJson())                             .onSuccess(rs -> rc.response().end(rs))                             .onFailure(rc::fail);                 });     } public Future getOrderByOrderId(Long orderId){     return mySQLPool.getConnection()       .compose(sc -> SqlTemplate.forQuery(sc,"SELECT * FROM `order` WHERE order_id=#{id}").mapTo(OrderPORowMapper.INSTANCE).execute(Map.of("id",orderId)).onComplete(ar -> sc.close()))       .flatMap(rs -> rs.size() == 0 ? Future.failedFuture("无此单号"):Future.succeededFuture(rs.iterator().next()));     }
  在这份代码里面数据库操作的返回值是Future,这难道是我们通过把jdbc操作丢到线程池中跑吗?仔细思考一下 如果是这样那么显然我们既没有减少阻塞时间,也没有降低线程开销。这个地方实际上是利用netty按照对应数据库的协议写出了一个新的响应式的数据库访问client。因此这里没有任何的线程在阻塞,即DB处理时间长的瓶颈并不会阻碍我们处理新的请求。
  思考这样一个情况,我们的httpclient,db client,redis client全是异步实现而且他们公用同一组线程作为Eventloop,那么这一套异步工具集下来是不是可以有效地提高我们的吞吐量?事实上,golang的协程网络库就是类似于这样。  性能好就代表一切吗?或者响应式存在什么问题
  从C10K角度来看,nio确实是一个很好的解决方案,Tomcat底层也是基于nio,但是为什么到业务处理层我们还是同步的呢?或者说为什么业务层不也使用异步响应式思想呢?
  我这里给出一个比较常见的响应式操作,开启事务然后查询最后回滚
  堆栈
  首先响应式是基于事件的,在api的表现上就是write(buffer,callbcak),一旦业务复杂起来回调地狱势必会出现,哪怕我们将其用promise/future改造也只是将回调打平了而已其实没有解决实际问题,同时回调还存在一个问题——会丢失大量堆栈信息,仅仅保留那些被捕获进来的状态。
  这一点很好理解,当你给这个一时半会没法完成的IO事件挂一个回调后,程序此时就执行完了OutFunction函数,因此退栈了,等他的IO完成后发现有个事件该执行了(runnable.run)就去执行,此时原来的栈已经推掉了,你没法在回调的堆栈里面看到原来的stack trace了
  我们丢失了堆栈即意味着丢失了函数的嵌套关系,就很难找到到底是谁调用了这个函数,是哪一个放置了回调,这一点在出问题要排查时是非常致命的
  ps:你仔细观察栈顶的函数名,实际上我们可以通过生成的lambda名来找一找,不过这是特殊情况了
  再比如说思考这样一个代码
  当第二行出现问题时,我没法从堆栈的信息里面获取到前后的操作详情  future.map(l -> {})       .flatmap(l -> {})  调试
  请看如下的代码
  一旦回调嵌套回调出现问题你很难去了解函数之间的调用关系,这一点对debug是致命的缺陷,因此你在idea里面debug的时候不得不把有先后关系的回调里面打满断点然后利用执行到断点的方式去debug,而不能打一个断点向下执行  生态兼容性
  这里直接给一个结论,完全无法无缝兼容。
  首先是线程模型完全不一致
  请求A到达服务器,解析后开始处理业务逻辑,该查数据库了,此时向数据库发送请求,由于数据库 client是非阻塞异步的,此时请求A对应的数据库响应还未返回没有触发后续事件,相当于请求A被"挂 起"了,此时eventloop就可以接收请求B,一直执行到请求数据库,若此时请求A的数据库响应已经到达 则触发了后续事件,eventloop再"恢复"请求A的处理直到写出请求A的响应 类似于一种交错处理,在每一个异步点挂起当前的请求(异步点就是那些需要发起异步方法的,比如请 求一个远端数据,或者线程池跑一个长时间任务,差不多就是一个方法返回future就是异步方法
  此时不同的任务交替跑在java线程上面,此时ThreadLocal就失效了,MDC这种依赖于ThreadLocal的就完全没办法使用了。
  即我们建立在单线程处理情况假设上的一些无侵入传参生态就完全失败了
  而为他带来性能提升的核心准则——不要阻塞事件循环——同时也使其与原有的同步生态隔离开来,这是两套完全不同的代码风格,这是很难以共存的,我们只能去复用很少一部分java的第三方包生态 很多中间件的SDK需要重写。这就是java后端性能提升的面对的问题,或许你用netty再加上graalvm aot支持可以建立一个性能很不错的网关,但是你用那些去写业务,很多东西都需要从0开始做起,这一点就是很多人提到的维护性问题。我已经不止一次看到有些同学在回调中直接去调用一个阻塞api了。  概念众多且不便于书写
  基于回调进行处理,其实类似于人肉进行cps变换,开发的便利性就会急剧下降。而从控制流角度来看,你想象一下,你调用多个异步操作,是不是从你的主控制流fork出来多个并发控制流?这些多出来的控制流是不太可控的,如果这些fork出来的控制流也会fork出新的控制流呢?如果此时还涉及到资源的释放呢?(请参考结构化并发)
  比如说onSuccess,OnFailure这种函数就是在模拟if..else,recoverWith模拟try..catch,在命令式代码中都很好书写,但是一旦开始用函数来模拟就非常难以理解和掌控了。本来若我们自己掌控不住代码还可以通过静态分析工具来帮助我们,但是切换到响应式模式,主流的静态分析工具也没法发挥作用。
  有一些库不只是简单的的回调便利化,还引入了一堆比较学院派的概念来模拟更多的结构,比如说project reactor,reactiveX,Mutiny!等,你需要理解各种稀奇古怪的操作符,上下游等概念才能比较有把握的去写出正确代码。我并不否认这些库在被压,容错中的优雅实现,但是我们的原则应该是用20%的理解就可以应对80%的代码,实际上这些库带来了很大的理解成本。  kotlin是不是可以来拯救世界呢?
  众所周知,kotlin号称better java,同样也是我最喜欢的jvm语言,它有个重量级特性——coroutine,我们都知道go的goroutine实际上是一种runtime提供的功能,jvm显然没有对应的功能,kotlin-coroutine实际上是一种语法糖——CPS变化的语法糖,即一种无栈协程的实现
  看这个代码,全程都是同步的 甚至可以try..catch..    suspend fun selectMessageRecordBySender(senderId:Int):List{     try{       val connection = pool.connection.await()        val res = SqlTemplate.forQuery(connection,"SELECT * FROM message_record WHERE sender = #{sender}")         .collecting(MessageRecord.collector)         .execute(mapOf("sender" to senderId))         .await()        return res.value()     }catch(t : Throwable){         throw wrap(t)     }   }
  甚至在idea里面可以串行的形式断点调试 kotlinlang.org/docs/debug-…
  是不是感觉 这就是最终结果了?响应式框架+kt coroutine就可以完全胜任任务了?
  错了!我们先来看看他的原理  堆栈?
  首先suspend 的本质,就是 CallBack。
  等等continuation 又是什么?它就是代表程序剩下的部分
  实际上来讲它等价于  getUserInfo(new CallBack() {     @Overridepublic void onSuccess(String user) {         if (user != null) {             System.out.println(user);             getFriendList(user, new CallBack() {                 @Overridepublic void onSuccess(String friendList) {                     if (friendList != null) {                         System.out.println(friendList);                         getFeedList(friendList, new CallBack() {                             @Overridepublic void onSuccess(String feed) {                                 if (feed != null) {                                     System.out.println(feed);                                 }                             }                         });                     }                 }             });         }     } });
  这些是编译器帮我们做的脏活而已,其本质还是回调,因此我们之前的问题还是没有解决——堆栈还是会丢失  染色?
  接着就是另外的问题了,suspend函数只能被suspend函数调用,也就是说它具有传染性,一直到顶层都需要是suspend的函数,然后相当于污染了整条调用链路,如果一门新语言,从标准库到上层,都是全 suspend 的还好一点,但是对于有些历史包袱的语言,有些库已经是非 suspend 的,这个染色的处理就很难受。
  同时Future也是这个问题,所有返回的值不再是一个普通的值了,而是一个 Future,需要用 map 函数解出来。一层一层往上染色,整个调用链路都变成 Future 的。
  简单来说kt只是解决了表面的异步转同步的问题,而非解决核心问题  触手可及但是不够好的未来——loom
  这些响应式api被创造出来不是因为它们更容易编写和理解,甚至它们实际上更难以弄明白;不是因为它们更容易调试或分析——甚至会更困难(它们甚至不会产生有意义的堆栈跟踪);并不是因为他们的代码结合比同步的api好——他们的结合不那么优雅;不是因为它们更适合语言中的其他部分,或者与现有代码集成得很好,而是因为并行性的软件单元——线程——的实现从内存和性能的角度来看是不够的。由于抽象的运行时性能问题,一个好的、自然的抽象被抛弃,而倾向于一个不那么自然的抽象,这是一个可悲的现状。
  为了改变这一切,Project loom——即将在jdk19 preview的特性(2022年7月24日)——为jvm提供以少数内核线程支持海量用户态线程的有栈协程实现。  它解决了什么问题?
  通过引入runtime支持的Continuation结构,重写网络库并且提供java.lang.Thread的子类VitrualThread,做到了只要简单替换线程池实现就可以获得类似于go但是是协作式的用户态线程的能力,没有函数染色的副作用,从而直接解决了生态不兼容的问题,同时也给予了旧有代码升级最小化改动的帮助。
  从前我们需要自己手写EventLoop,费劲地重新实现一遍协议解析只是为了提供更好的性能条件来做迁移,现在 只要开启一个虚拟线程 就像是goalng写一个go关键字一样简单(甚至于你可以用kotlin模拟出一个go关键字goroutine.kt),旧有生态的bio原地从阻塞内核线程升级到阻塞用户态线程,再也不需要开那么多内核线程来处理并发了。  Thread.startVirtualThread(() -> {     System.out.println("Hello, Loom!"); });
  Thread::currentThread,LockSupport::park,LockSupport::unpark,Thread::sleep,也对此做了适配,这意味着我们那些基于J.U.C包的并发工具仍旧可以使用。
  羡慕go的channel?J.U.C的BlockingQueue作为对标完全没有问题
  关键要点:  虚拟线程就是Thread——无论是在代码中,runtime中,调试器中还是在profiler中  虚拟线程不是对内核线程的包装,而是一个Java实例  创建一个虚拟线程是非常廉价的,——您可以拥有数百万个并且无需池化它  阻塞一个虚拟线程是非常廉价的,——您可以随意使用同步代码  无需在编程语言层面做任何更改  可插拔的调度器可以为异步编程提供更好的灵活性
  等等?为异步编程提供更好的灵活性?loom能为异步编程做什么?
  只要简单为它写个封装器就可以方便地在同步生态里面使用异步代码,轻松异步转同步而无需引入其他的库,甚至相对于原有的异步操作开火车,这种性能损耗非常少——而且堆栈连续。  public Future asyncFunction(){...} public String asyncFunctionWrapper(){     var t = Thread.currentThead();     var f = asyncFunction.onComplete(v -> LockSupport.unpark(t));     LockSupport.park(t);     if(f.success()) return f.get();     throw f.cause(); } //运行在虚拟线程中 public void fun(){    var s = asyncFunctionWrapper();    var s1 = asyncFunctionWrapper(); }  不够好是什么意思?
  先引入一个loom中的概念。pin
  如果虚拟线程被挂载到载体线程上,且处于无法卸载的状态,我们就说它被"pin"到它的载体线程上。如果一个虚拟线程在pin时阻塞了,它就阻塞了它的载体。这种行为仍然是正确的,但是在虚拟线程阻塞期间,它会持有工作线程,使得其他虚拟线程无法使用它。
  在当前的Loom实现中,虚拟线程可以被固定在两种情况下:当堆栈上有一个本机帧时——当Java代码调用本机代码(JNI),然后调用回Java时——以及在一个sychronized块或方法中。在这些情况下,阻塞虚拟线程将阻塞承载它的物理线程。一旦本机调用完成或监视器释放(synchronized块/方法退出),线程就被解除锁定。
  那我不用不就好了?而且原来的网络IO中的sychronized也被重写了,这有什么问题?
  来看一个我们经常使用的jdbc的实现——MySQL-connectorJ的堆栈检测。
  com.mysql.cj开头的堆栈的栈底有一个sychronized关键字加持的方法以防止多个线程读取同一个socket,因此在这里我们的线程就pin住了需要等待IO结束,这样又退回到原来的内核线程实现了
  除了jdbc,spring内嵌的Tomcat也有这个问题  Thread[#44,ForkJoinPool-1-worker-1,5,CarrierThreads]    ....     com.example.demo.DemoApplication.hello(DemoApplication.java:37)     java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)     java.base/java.lang.reflect.Method.invoke(Method.java:578)     org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) <== monitors:1    .....
  java的有栈协程非常美好 很可惜当前的应用无法无缝迁移,这一点就是为什么我说loom是触手可及但是不够好  总结
  我现在可以回答题目的问题了 我借用官方文档的一句话来说——
  Project Loom aims to drastically reduce the effort of writing, maintaining, and observing high-throughput concurrent applications that make the best use of available hardware.
  Project Loom旨在大幅减少编写、维护和观察高吞吐量并发应用程序的工作量,以便于充分利用可用硬件

结节包块多因肝郁气滞,一副古方八味中药,远离血瘀气郁,散结节古人有句话是这样讲的气结则血凝。这是什么意思呢?就说这人体中的气要是郁结了,那么这个血就会凝滞,从而出现血瘀的问题。说到这,就不得不提一下这气与血之间的关系了。对此古语云气为血之帅央视主持人杨帆一家四口其乐融融,就是大女儿让我头痛2019年,越战越勇的录制当天恰好是主持人杨帆的生日。那天节目组加上主持人给杨帆准备了惊喜,推完蛋糕出来以后还推出了一个大礼物盒。在众人都疑惑会是什么的时候,礼物盒里突然蹦出了一个中老年人生活中注意6个慢半拍,对健康很有益处人年龄大了,身体各项机能下降,思维能力也变差了,反应也逐渐迟钝,而且心脑血管也越来越脆弱,如果不慢下来最怕出现某些意外。因此,我们无论做什么事情都要学会慢半拍,这对自己的健康颇有好天麻能长期服用吗?有没有毒副作用?天麻为兰科植物天麻的干燥块茎,味甘,性平,归肝经,功能平肝息风止痉。用于头痛眩晕,肢体麻木,小儿惊风,癫痫抽搐,破伤风。生活中有的人为了疾病和养生的需求,需要将长天麻打粉做成胶囊或45岁后,身体出现这三个症状,可能离长寿越来越远,及时纠正人人能科普,处处有新知虽然随着生活水平的提高,人们的寿命相比较以前,已经有了很大的进步,但是人们还是希望更加长寿,总希望能活得更加久一点。但是长寿向来都是比较困难的,不是我们说想长翟天临新剧换脸效果差,神态油腻侧脸一模一样,一眼就能认出原身前排好奇,近期毕业的小伙伴们忙完了吗,有没有抽空问候一下翟天临,知道他有剧要播出了吗?当然,翟天临本人还是没有复出的,已经定档并放出预告的新剧名为买定离手我爱你,虽然是他主演的剧集酸梅汤里的乌梅,原来有这么多功效导读陶御风先生结合古籍历代医家经验以及自己的临床体会写就的乌梅临床论治备参17条,值得学习与收藏。一hr诸病多生于肝,肝为五脏之贼,故五脏之中惟肝最难调理。乌梅最能补肝,且能敛肝,夏天能不能喝中药?中医专家告诉你其实您更应该关心这些问题不知从何时起,总会听到有人说夏天不能喝中药,也不知道他们的依据从何而来。总有人担心夏天喝中药了更疲倦夏天吃中药上火夏天的中药容易变质今天一起跟大家探讨一下夏天中药容易变质?这个担心高血压,头晕目眩,心烦易怒,原来是肝阳上亢!这张方子都能化解今天这篇文章,我们来聊一聊高血压的一个证型肝阳上亢证。中医根据高血压的症状,将其归属为眩晕。根据不同的症状表现,又分了不同的证型,其中一种叫肝阳上亢证。肝是风木之脏,主动主升,肝阳五十多岁的女人凹造型,一定要有三件神器,时髦洋气又减龄五十多岁的女人,已经是阿姨辈的年纪了。没有了工作的压力,生活上也更加惬意。享受退休后的美好生活,拍照旅游凹造型,成为五十多岁女人最日常的活动。想要在同龄人中表现得更加出彩,一定要有春养阳胜良药,无论男女,建议多吃5种辛味,一年体质差不了俗话说春夏养阳,秋冬养阴,进入春天,气温开始逐渐回升,阳气随之处于升发之势,万物开始生长繁荣。阳气乃生命之本,春季保养身体要注重阳气的保护,生活上要注意规律作息,多到户外呼吸新鲜空
第一次做医美项目,我选择了光子嫩肤投稿光子嫩肤仪器前几天和朋友聚餐,我发现她神采焕发的,我问是不是她老公给她送什么大礼了,结果并没有,我就奇怪了,怎么突然变了个人一样?几经追问之下,她才告诉我,她最近在了解医疗美容穿卫衣不一定要配打底裤,这3种穿法不仅气质减龄,还很显嫩卫衣休闲舒适,是日常穿搭中出场率较高的服装之一,尤其是在冬末初春的过渡时期,叠穿单穿都非常不错。不过,有很大一部分姐妹穿卫衣总是喜欢搭配打底裤,虽说这种穿搭方法简单好驾驭,但确实很曼联内部分裂成两派系C罗领衔葡语帮与队友不和太阳报报道,在曼联目前混乱的局势中,最新消息显示曼联更衣室分为不同的两个派系。朗尼克上任没过多久,他已经承认队内有很多不开心的球星。在主场不敌狼队后,情况更加糟糕,C罗已经与经纪人库里单节4中0送4次助攻,勇士被打14比0后追分,莫兰特15分抢眼北京时间1月12日,NBA常规赛,勇士对阵灰熊,这是西部焦点对决。首节比赛,灰熊打出14比0一度拉开分差,勇士追分,库里单节4中0只靠罚球拿到3分,不过送出4次助攻,灰熊28比24恭喜!女排联赛最佳主攻正式公开恋情,男友比她小3岁英俊帅气随着时间的推移,中国女排超级联赛已经正式告一段落,姑娘们经过这段时间的征战,终于能好好享受一下假期时光。毕竟在联赛开打的40多天时间里,大家几乎就没有多少休息的机会,一直都处于高度库里我可以击败1996年的公牛队预计以42获胜!美国体育媒体在2020年4月评选出历史上最伟大的五支球队。第一名是在公牛队的199596赛季。直到五年前的金州勇士队出现,当年的迈克尔乔丹斯科蒂皮蓬丹尼斯罗德曼卢克朗利托尼库科奇等NBA第12周总得分榜前5NBA第12周战罢,本周总得分榜前5为No。1凯文杜兰特篮网队986分球队25胜14负64。1胜率暂时位居东部第2No。2特雷杨老鹰队981分球队17胜22负43。6胜率暂时位居东在役足坛常青树,日本两个,中国一个足球运动是一个高强度的运动项目,受限于身体机能,一般球员在三十岁就进入职业生涯的晚期,三十五岁大多数足球运动员已经退役了。可有些足球运动员,出于对足球运动的热爱,不断突破个体机能的扣篮王又伤了!自己把自己弄伤了,真的太惨了开局仅仅半分钟,德里克琼斯就以一种奇怪的方式弄伤了自己的右腿。他抱着球,身体后仰着落地,右腿承受了全身的重力,这让他的腿部关节反向弯曲,这个承担了这支务实的公牛全部观赏性的汉子,就最大爆冷全神班TES不敌HYA主播队,弹幕直接炸裂德玛西亚杯迎来了第二日的赛程(A组),最终当日的10场比赛战罢,赛果如下图所示。WE和TES战队都是以31的比分晋级到了八强之中!当日的比赛中,表现最为超出大家预期的是HYA主播队Uzi看TES碾压WE为JKL欢呼HYA主播人傻了,这是战胜我们的WE?在刚刚结束的TES和WE的德玛西亚杯比赛中,TES轻松战胜WE取得了比赛的胜利。TES的阵容为奥恩,皇子,维克托,金克斯,锤石。WE阵容为鳄鱼,佛耶戈,飞机,厄斐琉斯,露露。开局T