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

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旨在大幅减少编写、维护和观察高吞吐量并发应用程序的工作量,以便于充分利用可用硬件

时政Vlog丨羌寨里寻年味儿春节前夕,习近平总书记通过视频连线看望慰问基层干部群众,向全国各族人民致以新春的美好祝福。在四川省绵阳市北川羌族自治县的石椅村,有200多人在村里的文化广场通过视频看到了总书记,听吉尔吉斯斯坦将对网红博主采取实名制吉尔吉斯斯坦总统府近日更新媒体法草案,新草案参考了2022年11月成立的工作组代表意见和建议,该工作组是在相关独立专家和律师的倡议下成立的。值得注意的是,媒体法最新草案中有两项条款车友们开车回家前可别用这些药物,容易发生药驾交通安全千万条,不药驾也是其中一条。服用某些药物后也不能开车。尤其近期不少阳康还在吃止咳药消炎药感冒药等,需警惕发生药驾的情况。药驾的危害程度不亚于酒驾广东祈福医院药学部主任蒋琳兰不怪丁俊晖,41岁潘晓婷为何还是单身?择偶标准曝光,球迷很心疼说到丁俊晖和潘晓婷相信很多球迷不会陌生,他们是中国台球的骄傲,曾经被称为金童玉女,丁俊晖是中国斯诺克一哥,潘晓婷是九球天后,此前很多球迷认为金童和玉女能喜结连理,那么中国台球有可能莱昂纳德367乔治1612鲍威尔26分快船胜马刺!NBA常规赛1月21日继续进行,最终,快船以131126战胜马刺,快船终结2连败。首节开始,双方上来打得难分难解,快船以1512稍稍领先后马车打出一波70反超比分!乔治和莫里斯接连锂王净利预增超10倍,受益于新能源汽车热销在锂电行业高景气度之下,龙头企业业绩迎来大丰收。1月18日,天齐锂业发布业绩预告,预计2022年全年实现归属于上市公司股东的净利润为231亿元至256亿元,同比增长1011。19至干货,兔年牛股荐!新能源领域的价值洼地,大浪淘沙后的现金奶牛值此新春佳节之际,股票君祝愿大家兔年大吉,新的一年发!发!发。正好趁新年休市之际股票君整理了几期后期可能翻N倍的股票跟大家分享探讨,我们不看形态,只看后期可能价值。原创整理不易,觉还有那么多人自称是地主的后代,这是为什么?地主是中国历史上对某一特殊群体的特定称谓。由于历史的原因,从某种意义上说,地主便代表着财富,还有剥削阶级的蕴意在我国的一定历史阶段,地主还是专政的对象。随着历史的发展,特别是我国经网友贪便宜1500买iPhone11,验机发现是翻新机,还改了底层数据!iPhone翻新机特别多,一般人根本看不出来。这些翻新机大多集中在某几个平台。而且大多是网店卖出去的,商家还编出各种理由忽悠小白!比如网友今天买的这台iPhone11,该机关于本机很难看清人脸,部分AppleTV用户反馈播放HDR内容出现问题IT之家1月24日消息,根据Reddit社区Twitter社交媒体和苹果支持论坛上的AppleTV用户反馈,在播放HDR内容时出现了问题。用户反馈最多的就是图像太暗或者褪色。IT之微软更新Win11SnippingTool截图工具,支持暂停录屏IT之家1月24日消息,不久前微软推出了一个新的SnippingTool应用程序,并内置了屏幕录制功能。虽然很高兴看到微软终于在其操作系统中提供了一个原生录屏工具,但用户发现,该应
实体凉透了?上半年净消失4700家门店,数字化转型箭在弦上2022,实体零售依旧艰难2022年,可能是未来5年最难的一年!零售行业凛冬将至!你可别不信,仅仅上半年,就有4700家实体店关闭,比如美特斯邦威关店351家!森马关店860家!最看铁塔凌云古银杏广东韶关南雄的坪田是观赏千年古银杏的好地方。在银杏树叶渐渐由翠绿变成金黄色的时候,我携老伴来到了坪田的景点。眼前一棵树身黑悠悠的千年古银杏铁塔凌云,称之为中国最美古树据说银杏原产中初冬的沈阳,真美!冬雪后,到处藏着姹紫嫣红与生机勃勃刚刚过去的周末,落叶与冬雪邂逅,让整个沈阳城都笼罩在浪漫中。虽然室外寒风瑟瑟,雪后还是有很多市民出门打卡,记录下被白雪覆盖的城市景色。那些街上玩乐的大人孩子们,为生活奔波在路上的外吃土豆是否会增加罹患心脏代谢疾病的风险?最新研究带来不同意见最近的研究表明,总体饮食和生活方式,而不是具体的马铃薯烹饪技术,影响着与马铃薯消费有关的健康结果。尽管马铃薯是一种充满营养的蔬菜,但它经常被挑出来作为一种需要限制的食物。事实上,一您知道五红汤是什么吗?有哪些功效呢?不妨来了解一下你知道五红汤是什么吗?其实五红汤的原料是生活中很常见的五种食材,红枣,红豆,红皮花生,红糖和枸杞。因为这五种食物都是红色,所以叫做五红汤。五红汤中,红枣有补血的作用,红衣花生能养血中医方法暖五脏,补足阳气好过冬入冬后,各地气候相继寒冷起来,五脏阳气充足才能发挥正常功能,中医有不少暖五脏的好方法。温阳补心防心病对于容易心慌肢冷畏寒失眠等阴寒体质的人群,日常不妨试试温阳补心的方法,有助预防心癌症患者最应该吃的一类蔬菜,原来是它阅读前请点关注,每天定时分享关于乳腺肿瘤及癌症知识,拥抱每一位肿瘤患者,让你在抗癌的路上不孤单蔬菜是均衡膳食的重要组成部分。患癌后,我们对营养的需求变得更为迫切,对食物的选择也变得提醒中老年人进入冬季,若有条件,5种营养食物常吃,平稳度冬冬季进补,来年打虎,这句俗语之所以代代流传,是因为它具有一定的合理性。特别是对于中老年人而言,相较于舒适的春季和秋季,以及炎热的夏季,冬季带来的寒风和低温给他们的日常生活带来不小的船网大战疲卡休NBA常规赛船网大战,篮网11095狂胜快船,喜提二连胜。老杜库里合砍50分,乔治尽力了,队友是缅北之王,拿钱就跑。伦纳德狂砍0分0板1失踪,伦纳德都打一战了,歇一辈子怎么了。完全狂野东部!凯尔特人反超雄鹿领跑,76人热火反弹,篮网仅第1211月15日,NBA东部的格局发生很大的变化,雄鹿再度输给老鹰,再加上凯尔特人的赢球,这样雄鹿让出东部榜首的位置,绿衫军升至第一。热火逆转击败西部豪强太阳,同时76人也实现了崛起。没好控卫只能靠内线,大王再砍20助中国男篮取两连胜,锁定世界杯名额北京时间今天凌晨,征战世预赛的中国男篮用一个并不能让人满意的过程和一个最好的结果,最终通过加时赛以8067战胜了主场作战的巴林队,提前两轮取得了参加明年男篮世界杯的名额。队长王哲林