专栏电商日志财经减肥爱情
投稿投诉
爱情常识
搭配分娩
减肥两性
孕期塑形
财经教案
论文美文
日志体育
养生学堂
电商科学
头戴业界
专栏星座
用品音乐

Java高并发革命!JDK19新特性虚拟线程(Virtual

  介绍
  虚拟线程具有和Go语言的goroutines和Erlang语言的进程类似的实现方式,它们是用户模式(usermode)线程的一种形式。
  在过去Java中常常使用线程池来进行平台线程的共享以提高对计算机硬件的使用率,但在这种异步风格中,请求的每个阶段可能在不同的线程上执行,每个线程以交错的方式运行属于不同请求的阶段,与Java平台的设计不协调从而导致:堆栈跟踪不提供可用的上下文调试器不能单步执行请求处理逻辑分析器不能将操作的成本与其调用方关联。
  而虚拟线程既保持与平台的设计兼容,同时又能最佳地利用硬件从而不影响可伸缩性。虚拟线程是由JDK而非操作系统提供的线程的轻量级实现:虚拟线程是没有绑定到特定操作系统线程的线程。平台线程是以传统方式实现的线程,作为围绕操作系统线程的简单包装。摘要
  向Java平台引入虚拟线程。虚拟线程是轻量级线程,它可以大大减少编写、维护和观察高吞吐量并发应用程序的工作量。目标允许以简单的每个请求一个线程的方式编写的服务器应用程序以接近最佳的硬件利用率进行扩展。允许使用java。lang。ThreadAPI的现有代码采用虚拟线程,并且只做最小的更改。使用现有的JDK工具可以方便地对虚拟线程进行故障排除、调试和分析。非目标移除线程的传统实现或迁移现有应用程序以使用虚拟线程并不是目标。改变Java的基本并发模型。我们的目标不是在Java语言或Java库中提供新的资料平行结构。StreamAPI仍然是并行处理大型数据集的首选方法。动机
  近30年来,Java开发人员一直依赖线程作为并发服务器应用程序的构件。每个方法中的每个语句都在一个线程中执行,而且由于Java是多线程的,因此执行的多个线程同时发生。
  线程是Java的并发单元:一段顺序代码,与其他这样的单元并发运行,并且在很大程度上独立于这些单元。
  每个线程都提供一个堆栈来存储本地变量和协调方法调用,以及出错时的上下文:异常被同一个线程中的方法抛出和捕获,因此开发人员可以使用线程的堆栈跟踪来查找发生了什么。
  线程也是工具的一个核心概念:调试器遍历线程方法中的语句,分析器可视化多个线程的行为,以帮助理解它们的性能。两种并发stylethreadperrequeststyle服务器应用程序通常处理彼此独立的并发用户请求,因此应用程序通过在整个请求持续期间为该请求分配一个线程来处理请求是有意义的。这种按请求执行线程的style易于理解、易于编程、易于调试和配置,因为它使用平台的并发单元来表示应用程序的并发单元。服务器应用程序的可伸缩性受到利特尔定律(LittlesLaw)的支配,该定律关系到延迟、并发性和吞吐量:对于给定的请求处理持续时间(延迟),应用程序同时处理的请求数(并发性)必须与到达速率(吞吐量)成正比增长。例如,假设一个平均延迟为50ms的应用程序通过并发处理10个请求实现每秒200个请求的吞吐量。为了使该应用程序的吞吐量达到每秒2000个请求,它将需要同时处理100个请求。如果在请求持续期间每个请求都在一个线程中处理,那么为了让应用程序跟上,线程的数量必须随着吞吐量的增长而增长。不幸的是,可用线程的数量是有限的,因为JDK将线程实现为操作系统(OS)线程的包装器。操作系统线程代价高昂,因此我们不能拥有太多线程,这使得实现不适合每个请求一个线程的style。如果每个请求在其持续时间内消耗一个线程,从而消耗一个OS线程,那么线程的数量通常会在其他资源(如CPU或网络连接)耗尽之前很久成为限制因素。JDK当前的线程实现将应用程序的吞吐量限制在远低于硬件所能支持的水平。即使在线程池中也会发生这种情况,因为这有助于避免启动新线程的高成本,但不会增加线程的总数。asynchronousstyle一些希望充分利用硬件的开发人员已经放弃了每个请求一个线程(threadperrequest)的style,转而采用线程共享(threadsharing)的style。请求处理代码不是从头到尾处理一个线程上的请求,而是在等待IO操作完成时将其线程返回到一个池中,以便该线程能够处理其他请求。这种细粒度的线程共享(其中代码只在执行计算时保留一个线程,而不是在等待IO时保留该线程)允许大量并发操作,而不需要消耗大量线程。虽然它消除了操作系统线程的稀缺性对吞吐量的限制,但代价很高:它需要一种所谓的异步编程style,采用一组独立的IO方法,这些方法不等待IO操作完成,而是在以后将其完成信号发送给回调。如果没有专门的线程,开发人员必须将请求处理逻辑分解成小的阶段,通常以lambda表达式的形式编写,然后将它们组合成带有API的顺序管道(例如,参见CompletableFuture,或者所谓的反应性框架)。因此,它们放弃了语言的基本顺序组合运算符,如循环和trycatch块。在异步样式中,请求的每个阶段可能在不同的线程上执行,每个线程以交错的方式运行属于不同请求的阶段。这对于理解程序行为有着深刻的含义:堆栈跟踪不提供可用的上下文调试器不能单步执行请求处理逻辑分析器不能将操作的成本与其调用方关联。当使用Java的流API在短管道中处理数据时,组合lambda表达式是可管理的,但是当应用程序中的所有请求处理代码都必须以这种方式编写时,就有问题了。这种编程style与Java平台不一致,因为应用程序的并发单元(异步管道)不再是平台的并发单元。对比
  threadperrequeststyle
  asynchronousstyle
  优点
  与Java平台的设计相协调,易于理解、易于编程、易于调试和配置
  这种细粒度的线程共享允许大量并发操作,而不需要消耗大量线程,提高可伸缩性
  缺点
  可用线程的数量是有限的,操作系统线程代价高昂,因此我们不能拥有太多线程,这使得实现不适合threadperrequeststyle
  这种编程style与Java平台不一致,因为应用程序的并发单元(异步管道)不再是平台的并发单元使用虚拟线程保留threadperrequeststyle
  为了使应用程序能够在与平台保持和谐的同时进行扩展,我们应该通过更有效地实现线程来努力保持每个请求一个线程的style,以便它们能够更加丰富。
  操作系统无法更有效地实现OS线程,因为不同的语言和运行时以不同的方式使用线程堆栈。然而,Java运行时实现Java线程的方式可以切断它们与操作系统线程之间的一一对应关系。正如操作系统通过将大量虚拟地址空间映射到有限数量的物理RAM而给人一种内存充足的错觉一样,Java运行时也可以通过将大量虚拟线程映射到少量操作系统线程而给人一种线程充足的错觉。虚拟线程是没有绑定到特定操作系统线程的线程。平台线程是以传统方式实现的线程,作为围绕操作系统线程的简单包装。
  threadperrequest样式的应用程序代码可以在整个请求期间在虚拟线程中运行,但是虚拟线程只在CPU上执行计算时使用操作系统线程。其结果是与异步样式相同的可伸缩性,除了它是透明实现的:
  当在虚拟线程中运行的代码调用Java。API中的阻塞IO操作时,运行时执行一个非阻塞操作系统调用,并自动挂起虚拟线程,直到稍后可以恢复。
  对于Java开发人员来说,虚拟线程是创建成本低廉、数量几乎无限多的线程。硬件利用率接近最佳,允许高水平的并发性,从而提高吞吐量,而应用程序仍然与Java平台及其工具的多线程设计保持协调。虚拟线程的意义
  虚拟线程是廉价和丰富的,因此永远不应该被共享(即使用线程池):应该为每个应用程序任务创建一个新的虚拟线程。
  因此,大多数虚拟线程的寿命都很短,并且具有浅层调用堆栈,执行的操作只有单个HTTP客户机调用或单个JDBC查询那么少。相比之下,平台线程是重量级和昂贵的,因此经常必须共享。它们往往是长期存在的,具有深度调用堆栈,并且在许多任务之间共享。
  总之,虚拟线程保留了可靠的threadperrequeststyle,这种style与Java平台的设计相协调,同时又能最佳地利用硬件。使用虚拟线程并不需要学习新的概念,尽管它可能需要为应对当今线程的高成本而养成的忘却习惯。虚拟线程不仅可以帮助应用程序开发人员它们还可以帮助框架设计人员提供易于使用的API,这些API与平台的设计兼容,同时又不影响可伸缩性。说明
  如今,java。lang的每一个实例。JDK中的线程是一个平台线程。平台线程在底层操作系统线程上运行Java代码,并在代码的整个生命周期中捕获操作系统线程。平台线程的数量仅限于操作系统线程的数量。
  虚拟线程是java。lang的一个实例。在基础操作系统线程上运行Java代码,但在代码的整个生命周期中不捕获该操作系统线程的线程。这意味着许多虚拟线程可以在同一个OS线程上运行它们的Java代码,从而有效地共享它们。平台线程垄断了一个珍贵的操作系统线程,而虚拟线程却没有。虚拟线程的数量可能比操作系统线程的数量大得多。
  虚拟线程是由JDK而非操作系统提供的线程的轻量级实现。它们是用户模式(usermode)线程的一种形式,已经在其他多线程语言中取得了成功(例如,Go中的goroutines和Erlang的进程)。在Java的早期版本中,用户模式线程甚至以所谓的绿线程为特色,当时OS线程还不成熟和普及。然而,Java的绿色线程都共享一个OS线程(M:1调度),并最终被平台线程超越,实现为OS线程的包装器(1:1调度)。虚拟线程采用M:N调度,其中大量(M)虚拟线程被调度在较少(N)操作系统线程上运行。虚拟线程VS平台线程简单示例
  开发人员可以选择使用虚拟线程还是平台线程。下面是一个创建大量虚拟线程的示例程序。该程序首先获得一个ExecutorService,它将为每个提交的任务创建一个新的虚拟线程。然后,它提交10000项任务,等待所有任务完成:try(varexecutorExecutors。newVirtualThreadPerTaskExecutor()){IntStream。range(0,10000)。forEach(i{executor。submit((){Thread。sleep(Duration。ofSeconds(1));returni;});});}executor。close()iscalledimplicitly,andwaits复制代码
  本例中的任务是简单的代码(休眠一秒钟),现代硬件可以轻松支持10,000个虚拟线程并发运行这些代码。在幕后,JDK在少数操作系统线程上运行代码,可能只有一个线程。
  如果这个程序使用ExecutorService为每个任务创建一个新的平台线程,比如Executors。newCachedThreadPool(),那么情况就会大不相同。ExecutorService将尝试创建10,000个平台线程,从而创建10,000个OS线程,程序可能会崩溃,这取决于计算机和操作系统。
  相反,如果程序使用从池中获取平台线程的ExecutorService(例如Executors。newFixedThreadPool(200)),情况也不会好到哪里去。ExecutorService将创建200个平台线程,由所有10,000个任务共享,因此许多任务将按顺序运行,而不是并发运行,而且程序将需要很长时间才能完成。对于这个程序,一个有200个平台线程的池只能达到每秒200个任务的吞吐量,而虚拟线程达到每秒10,000个任务的吞吐量(在充分预热之后)。此外,如果示例程序中的10000被更改为1000000,那么该程序将提交1,000,000个任务,创建1,000,000个并发运行的虚拟线程,并且(在足够的预热之后)实现大约1,000,000任务秒的吞吐量。
  如果这个程序中的任务执行一秒钟的计算(例如,对一个巨大的数组进行排序)而不仅仅是休眠,那么增加超出处理器核心数量的线程数量将无济于事,无论它们是虚拟线程还是平台线程。
  虚拟线程并不是更快的线程它们运行代码的速度并不比平台线程快。它们的存在是为了提供规模(更高的吞吐量),而不是速度(更低的延迟)。它们的数量可能比平台线程多得多,因此根据Little’sLaw,它们能够实现更高吞吐量所需的更高并发性。
  换句话说,虚拟线程可以显著提高应用程序的吞吐量,在如下情况时:并发任务的数量很多(超过几千个)工作负载不受CPU限制,因为在这种情况下,比处理器核心拥有更多的线程并不能提高吞吐量
  虚拟线程有助于提高典型服务器应用程序的吞吐量,因为这类应用程序由大量并发任务组成,这些任务花费了大量时间等待。
  虚拟线程可以运行平台线程可以运行的任何代码。特别是,虚拟线程支持线程本地变量和线程中断,就像平台线程一样。这意味着处理请求的现有Java代码很容易在虚拟线程中运行。许多服务器框架将选择自动执行此操作,为每个传入请求启动一个新的虚拟线程,并在其中运行应用程序的业务逻辑。
  下面是一个服务器应用程序示例,它聚合了另外两个服务的结果。假设的服务器框架(未显示)为每个请求创建一个新的虚拟线程,并在该虚拟线程中运行应用程序的句柄代码。然后,应用程序代码创建两个新的虚拟线程,通过与第一个示例相同的ExecutorService并发地获取资源:voidhandle(Requestrequest,Responseresponse){varurl1。。。varurl2。。。try(varexecutorExecutors。newVirtualThreadPerTaskExecutor()){varfuture1executor。submit(()fetchURL(url1));varfuture2executor。submit(()fetchURL(url2));response。send(future1。get()future2。get());}catch(ExecutionExceptionInterruptedExceptione){response。fail(e);}}StringfetchURL(URLurl)throwsIOException{try(varinurl。openStream()){returnnewString(in。readAllBytes(),StandardCharsets。UTF8);}}复制代码
  这样的服务器应用程序使用简单的阻塞代码,可以很好地扩展,因为它可以使用大量虚拟线程。
  NewVirtualThreadPerTaskExector()并不是创建虚拟线程的唯一方法。新的java。lang。Thread。Builder。可以创建和启动虚拟线程。此外,结构化并发提供了一个更强大的API来创建和管理虚拟线程,特别是在类似于这个服务器示例的代码中,通过这个API,平台及其工具可以了解线程之间的关系。虚拟线程是一个预览API,默认情况下是禁用的
  上面的程序使用Executors。newVirtualThreadPerTaskExector()方法,因此要在JDK19上运行它们,必须启用以下预览API:使用javacrelease19enablepreviewMain。java编译该程序,并使用javaenablepreviewMain运行该程序;或者:在使用源代码启动程序时,使用javasource19enablepreviewMain。java运行程序;或者:在使用jshell时,使用jshellenablepreview启动它。不要共享(pool)虚拟线程
  开发人员通常会将应用程序代码从传统的基于线程池的ExecutorService迁移到每个任务一个虚拟线程的ExecutorService。与所有资源池一样,线程池旨在共享昂贵的资源,但虚拟线程并不昂贵,而且从不需要共享它们。
  开发人员有时使用线程池来限制对有限资源的并发访问。例如,如果一个服务不能处理超过20个并发请求,那么通过提交给大小为20的池的任务将确保执行对该服务的所有访问。因为平台线程的高成本使得线程池无处不在,所以这个习惯用法也变得无处不在,但是开发人员不应该为了限制并发性而将虚拟线程集中起来。应该使用专门为此目的设计的构造(如信号量semaphores)来保护对有限资源的访问。这比线程池更有效、更方便,也更安全,因为不存在线程本地数据从一个任务意外泄漏到另一个任务的风险。观测
  编写清晰的代码并不是故事的全部。对于故障排除、维护和优化来说,清晰地表示正在运行的程序的状态也是必不可少的,JDK长期以来一直提供调试、概要分析和监视线程的机制。这样的工具对虚拟线程也应该这样做也许要适应它们的大量数据因为它们毕竟是java。lang。Thread的实例。
  Java调试器可以单步执行虚拟线程、显示调用堆栈和检查堆栈帧中的变量。JDKFlightRecorder(JFR)是JDK的低开销分析和监视机制,可以将来自应用程序代码的事件(比如对象分配和IO操作)与正确的虚拟线程关联起来。
  这些工具不能为以异步样式编写的应用程序做这些事情。在这种风格中,任务与线程无关,因此调试器不能显示或操作任务的状态,分析器也不能告诉任务等待IO所花费的时间。
  线程转储(threaddump)是另一种流行的工具,用于以每个请求一个线程的样式编写的应用程序的故障排除。遗憾的是,通过jstack或jcmd获得的JDK传统线程转储提供了一个扁平的线程列表。这适用于数十或数百个平台线程,但不适用于数千或数百万个虚拟线程。因此,我们将不会扩展传统的线程转储以包含虚拟线程,而是在jcmd中引入一种新的线程转储,以显示平台线程旁边的虚拟线程,所有这些线程都以一种有意义的方式进行分组。当程序使用结构化并发时,可以显示线程之间更丰富的关系。
  因为可视化和分析大量的线程可以从工具中受益,所以jcmd除了纯文本之外,还可以发布JSON格式的新线程转储:jcmdpidThread。dumptofileformatjsonfile复制代码
  新的线程转储格式列出了在网络IO操作中被阻塞的虚拟线程,以及由上面所示的newthreadpertaskExecutorService创建的虚拟线程。它不包括对象地址、锁、JNI统计信息、堆统计信息以及传统线程转储中出现的其他信息。此外,由于可能需要列出大量线程,因此生成新的线程转储并不会暂停应用程序。
  下面是这样一个线程转储的示例,它取自类似于上面第二个示例的应用程序,在JSON查看器中呈现(单击可放大):
  由于虚拟线程是在JDK中实现的,并且不绑定到任何特定的操作系统线程,因此它们对操作系统是不可见的,操作系统不知道它们的存在。操作系统级别的监视将观察到,JDK进程使用的操作系统线程比虚拟线程少。调度
  为了完成有用的工作,需要调度一个线程,也就是分配给处理器核心执行。对于作为OS线程实现的平台线程,JDK依赖于OS中的调度程序。相比之下,对于虚拟线程,JDK有自己的调度程序。JDK的调度程序不直接将虚拟线程分配给处理器,而是将虚拟线程分配给平台线程(这是前面提到的虚拟线程的M:N调度)。然后,操作系统像往常一样调度平台线程。
  JDK的虚拟线程调度程序是一个在FIFO模式下运行的工作窃取(workstealing)的ForkJoinPool。调度程序的并行性是可用于调度虚拟线程的平台线程的数量。默认情况下,它等于可用处理器的数量,但是可以使用系统属性jdk。viralThreadScheduler。allelism对其进行调优。注意,这个ForkJoinPool不同于公共池,例如,公共池用于并行流的实现,公共池以LIFO模式运行。虚拟线程无法获得载体(即负责调度虚拟线程的平台线程)的标识。由Thread。currentThread()返回的值始终是虚拟线程本身。载体和虚拟线程的堆栈跟踪是分离的。在虚拟线程中抛出的异常将不包括载体的堆栈帧。线程转储不会显示虚拟线程堆栈中其载体的堆栈帧,反之亦然。虚拟线程不能使用载体的线程本地变量,反之亦然。
  此外,从Java代码的角度来看,虚拟线程及其载体平台线程临时共享操作系统线程的事实是不存在的。相比之下,从本机代码的角度来看,虚拟线程及其载体都在同一个本机线程上运行。因此,在同一虚拟线程上多次调用的本机代码可能会在每次调用时观察到不同的OS线程标识符。
  调度程序当前没有实现虚拟线程的时间共享。分时是对消耗了分配的CPU时间的线程的强制抢占。虽然在平台线程数量相对较少且CPU利用率为100的情况下,分时可以有效地减少某些任务的延迟,但是对于一百万个虚拟线程来说,分时是否有效尚不清楚。执行
  要利用虚拟线程,不必重写程序。虚拟线程不需要或期望应用程序代码显式地将控制权交还给调度程序;换句话说,虚拟线程不是可协作的。用户代码不能假设如何或何时将虚拟线程分配给平台线程,就像它不能假设如何或何时将平台线程分配给处理器核心一样。
  为了在虚拟线程中运行代码,JDK的虚拟线程调度程序通过将虚拟线程挂载到平台线程上来分配要在平台线程上执行的虚拟线程。这使得平台线程成为虚拟线程的载体。稍后,在运行一些代码之后,虚拟线程可以从其载体卸载。此时平台线程是空闲的,因此调度程序可以在其上挂载不同的虚拟线程,从而使其再次成为载体。
  通常,当虚拟线程阻塞IO或JDK中的其他阻塞操作(如BlockingQueue。take())时,它将卸载。当阻塞操作准备完成时(例如,在套接字上已经接收到字节),它将虚拟线程提交回调度程序,调度程序将在运营商上挂载虚拟线程以恢复执行。
  虚拟线程的挂载和卸载频繁且透明,并且不会阻塞任何OS线程。例如,前面显示的服务器应用程序包含以下代码行,其中包含对阻塞操作的调用:response。send(future1。get()future2。get());复制代码
  这些操作将导致虚拟线程多次挂载和卸载,通常每个get()调用一次,在send(。。。)中执行IO过程中可能多次挂载和卸载。
  JDK中的绝大多数阻塞操作将卸载虚拟线程,从而释放其载体和底层操作系统线程,使其承担新的工作。但是,JDK中的一些阻塞操作不会卸载虚拟线程,因此阻塞了其载体和底层OS线程。这是由于操作系统级别(例如,许多文件系统操作)或JDK级别(例如,Object。wait())的限制造成的。这些阻塞操作的实现将通过暂时扩展调度程序的并行性来补偿对OS线程的捕获。因此,调度程序的ForkJoinPool中的平台线程的数量可能会暂时超过可用处理器的数量。可以使用系统属性jdk。viralThreadScheduler。maxPoolSize调优调度程序可用的最大平台线程数。
  有两种情况下,在阻塞操作期间无法卸载虚拟线程,因为它被固定在其载体上:当它在同步块或方法内执行代码时,或当它执行本机方法或外部函数时。
  固定并不会导致应用程序不正确,但它可能会妨碍应用程序的可伸缩性。如果虚拟线程在固定时执行阻塞操作(如IO或BlockingQueue。take()),那么它的载体和底层操作系统线程将在操作期间被阻塞。长时间的频繁固定会通过捕获运营商而损害应用程序的可伸缩性。
  调度程序不会通过扩展其并行性来补偿固定。相反,可以通过修改频繁运行的同步块或方法来避免频繁和长时间的固定,并保护潜在的长IO操作来使用java。util。concurrent。locks。ReentrantLock。不需要替换不常使用的同步块和方法(例如,只在启动时执行)或保护内存操作的同步块和方法。一如既往,努力保持锁定策略的简单明了。
  新的诊断有助于将代码迁移到虚拟线程,以及评估是否应该使用java。util。concurrentlock替换同步的特定用法:当线程在固定时阻塞时,会发出JDKJFR事件。当线程在固定时阻塞时,系统属性jdk。tracePinnedThreads触发堆栈跟踪。使用Djdk。tracePinnedThreadsfull运行会在线程被固定时打印一个完整的堆栈跟踪,并突出显示保存监视器的本机框架和框架。使用Djdk。tracePinnedThreadsshort将输出限制为有问题的帧。内存使用和垃圾回收
  虚拟线程的堆栈作为堆栈块对象存储在Java的垃圾回收堆中。堆栈随着应用程序的运行而增长和缩小,这既是为了提高内存效率,也是为了容纳任意深度的堆栈(直到JVM配置的平台线程堆栈大小)。这种效率支持大量的虚拟线程,因此服务器应用程序中每个请求一个线程的风格可以继续存在。
  在上面的第二个例子中,回想一下,一个假设的框架通过创建一个新的虚拟线程并调用handle方法来处理每个请求;即使它在深度调用堆栈的末尾调用handle(在身份验证、事务处理等之后),handle本身也会产生多个虚拟线程,这些虚拟线程只执行短暂的任务。因此,对于每个具有深层调用堆栈的虚拟线程,都会有多个具有浅层调用堆栈的虚拟线程,这些虚拟线程消耗的内存很少。
  通常,虚拟线程所需的堆空间和垃圾收集器活动的数量很难与异步代码的数量相比较。一百万个虚拟线程至少需要一百万个对象,但是共享一个平台线程池的一百万个任务也需要一百万个对象。此外,处理请求的应用程序代码通常跨IO操作维护数据。每个请求一个线程的代码可以将这些数据保存在本地变量中:这些本地变量存储在堆中的虚拟线程堆栈中异步代码必须将这些数据保存在从管道的一个阶段传递到下一个阶段的堆对象中
  一方面,虚拟线程需要的堆栈帧布局比紧凑对象更浪费;另一方面,虚拟线程可以在许多情况下变异和重用它们的堆栈(取决于低级GC交互),而异步管道总是需要分配新对象,因此虚拟线程可能需要更少的分配。
  总的来说,每个请求线程与异步代码的堆消耗和垃圾收集器活动应该大致相似。随着时间的推移,我们希望使虚拟线程堆栈的内部表示更加紧凑。
  与平台线程堆栈不同,虚拟线程堆栈不是GC根,所以它们中包含的引用不会被执行并发堆扫描的垃圾收集器(比如G1)在stoptheworld暂停中遍历。这也意味着,如果一个虚拟线程被阻塞,例如BlockingQueue。take(),并且没有其他线程可以获得对虚拟线程或队列的引用,那么线程就可以被垃圾收集这很好,因为虚拟线程永远不会被中断或解除阻塞。当然,如果虚拟线程正在运行,或者它被阻塞并且可能被解除阻塞,那么它将不会被垃圾收集。
  当前虚拟线程的一个限制是G1GC不支持大型堆栈块对象。如果虚拟线程的堆栈达到区域大小的一半(可能小到512KB),那么可能会抛出StackOverfloError。具体变化java。lang。ThreadThread。Builder,Thread。ofVirtual(),和Thread。ofPlatform()是创建虚拟线程和平台线程的新API,例如:ThreadthreadThread。ofVirtual()。name(duke)。unstarted(runnable);复制代码创建一个新的未启动的虚拟线程duke。Thread。startVirtualThread(Runnable)是创建然后启动虚拟线程的一种方便的方法。Thread。Builder可以创建线程或ThreadFactory,后者可以创建具有相同属性的多个线程。Thread。isVirtual()测试是否一个线程是一个虚拟的线程。Thread。join和Thread。sleep的新重载接受等待和睡眠时间作为java。time。Duration的实例。新的final方法Thread。threadId()返回线程的标识符。现在不推荐使用现有的非final方法Thread。getId()。Thread。getAllStackTraces()现在返回所有平台线程的映射,而不是所有线程的映射。
  java。lang。ThreadAPI其他方面没有改变。构造器也无新变化。
  虚拟线程和平台线程之间的主要API差异是:公共线程构造函数不能创建虚拟线程。虚拟线程始终是守护进程线程,Thread。setDaemon(boolean)方法不能将虚拟线程更改为非守护进程线程。虚拟线程有一个固定的Thread。NORMPRIORITY优先级。Thread。setPriority(int)方法对虚拟线程没有影响。在将来的版本中可能会重新讨论这个限制。虚拟线程不是线程组的活动成员。在虚拟线程上调用时,Thread。getThreadGroup()返回一个名为VirtualThreads的占位符线程组。TheThread。BuilderAPI不定义设置虚拟线程的线程组的方法。使用SecurityManager集运行时,虚拟线程没有权限。虚拟线程不支持stop(),suspend(),或resume()方法。这些方法在虚拟线程上调用时引发异常。Threadlocalvariables
  虚拟线程支持线程局部变量(ThreadLocal)和可继承的线程局部变量(InheritableThreadLocal),就像平台线程一样,因此它们可以运行使用线程局部变量的现有代码。但是,由于虚拟线程可能非常多,所以应该在仔细考虑之后使用线程局部变量。
  特别是,不要使用线程局部变量在线程池中共享同一线程的多个任务之间共享昂贵的资源。虚拟线程永远不应该被共享,因为每个线程在其生存期内只能运行一个任务。我们已经从java。base模块中移除了许多线程局部变量的使用,以便为虚拟线程做准备,从而减少在使用数百万个线程运行时的内存占用。
  此外:TheThread。BuilderAPI定义了一个在创建线程时选择不使用线程局部变量的方法(amethodtooptoutofthreadlocalswhencreatingathread)。它还定义了一个方法来选择不继承可继承线程局部变量的初始值(amethodtooptoutofinheritingtheinitialvalueofinheritablethreadlocals)。当从不支持线程局部变量的线程调用时,ThreadLocal。get()返回初始值,ThreadLocal。set(T)抛出异常。遗留上下文类加载器(contextclassloader)现在被指定为像可继承的线程本地一样工作。如果在不支持线程局部变量的线程上调用Thread。setContextClassLoader(ClassLoader),那么它将引发异常。
  在某些用例中,作用域局部变量(Scopelocalvariables)可能是线程局部变量的更好替代方案。java。util。concurrent
  支持锁定的基本API,java。util。concurrent。LockSupport,现在支持虚拟线程:停靠虚拟线程将释放底层平台线程来执行其他工作,而取消停靠虚拟线程将安排其继续。对LockSupport的这种改变使得所有使用它的API(锁、信号量、阻塞队列等)在虚拟线程中调用时能够优雅地停放。
  此外:Executors。newThreadPerTaskExecutor(ThreadFactory)和Executors。newVirtualThreadPerTaskExecutor()创建一个ExecutorService。该Service为每个任务创建一个新线程。这些方法支持与使用线程池和ExecutorService的现有代码的迁移和互操作性。ExecutorService现在继承AutoCloseable,因此允许这个API与上面的示例所示的trywithresource结构一起使用。Future现在定义了获取已完成任务的结果或异常以及获取任务状态的方法。结合起来,这些添加使得使用Future对象作为流的元素变得很容易,过滤一个Future流来查找已完成的任务,然后映射它来获得一个结果流。对于为结构化并发提出的API添加,这些方法也很有用。Networking
  网络API在java。net和java。nio。channels包中的实现现在与虚拟线程一起工作:虚拟线程上的一个操作阻塞,例如,建立网络连接或从套接字读取,释放底层平台线程来做其他工作。
  为了允许中断和取消,java。net。Socket定义的阻塞IO方法、ServerSocket和DatagramSocket现在被指定为在虚拟线程中调用时是可中断的:中断套接字上被阻塞的虚拟线程将释放线程并关闭套接字。
  当从InterruptibleChannel获取时,这些类型套接字上的阻塞IO操作总是可中断的,因此这种更改使这些API在创建时的行为与从通道获取时的构造函数的行为保持一致。java。io
  Thejava。io包为字节和字符流提供API。这些API的实现是高度同步的,需要进行更改以避免在虚拟线程中使用被固定。
  在底层中,面向字节的输入输出流没有指定为线程安全的,也没有指定在读或写方法中阻塞线程时调用close()时的预期行为。在大多数情况下,使用来自多个并发线程的特定输入或输出流是没有意义的。面向字符的读写器也没有被指定为线程安全的,但是它们确实为子类公开了一个锁对象。除了固定外,这些类中的同步还存在问题和不一致;例如,InputStreamReader和OutputStreamWriter使用的流解码器和编码器在流对象而不是锁对象上进行同步。
  为了防止固定,现在的实现如下:BufferedInputStream,BufferedOutputStream,BufferedReader,BufferedWriter,PrintStream,和PrintWriter现在在直接使用时使用显式锁而不是监视器。当这些类被子类化时,它们与以前一样进行同步。InputStreamReader和OutputStreamWriter使用的流解码器和编码器现在使用与封闭的InputStreamReader或OutputStreamWriter相同的锁。
  更进一步并消除所有这些常常不必要的锁定超出了本文的范围。
  此外,BufferedOutputStream、BufferedWriter和OutputStreamWriter的流编码器使用的缓冲区的初始大小现在更小了,以便在堆中有许多流或写入器时减少内存使用如果有一百万个虚拟线程,每个线程在套接字连接上都有一个缓冲流,就可能出现这种情况。总结
  虚拟线程提供了使用线程池共享平台线程以达到异步之外的另一种更加可调试、可跟踪、可分析的高并发方式。
  曾经Java开发者们面对GO对高并发的友好支持只能干瞪眼,而现在让我们拥抱属于Java的高并发未来。
  原文链接:https:juejin。cnpost7146873337958891550

福建这3种另类小吃,本地人当作心头爱,外地人甚至没听说过说到福建小吃,外地人大多都会想到家附近的那家沙县小吃,似乎没去过福建的人们,基本上都是从这个地方开始了解的。其实福建的小吃非常多,除了开在全国各地的那个小吃店。还有很出名的,比如沙知名名媛出席富婆聚会!被曝脚踏两只船,外貌不断升级变网红脸本文编辑剧透社issac未经授权严禁转载,发现抄袭者将进行全网投诉香港知名男星TVB前艺人魏骏杰的前妻,被外界誉为知名名媛的张利华,最近几年可以说热议度颇高。除了当年和魏骏杰离婚闹女神变脸没人敢认?42岁未婚被封疯批美人,童年滤镜碎了一地猜猜这是谁关之琳?倪虹洁?温峥嵘?那英?熊黛林?苏芒?马苏?还是章小蕙?都不是哈,这是叶璇!该怎么形容看到这张动图的震撼呢大概就是忍不住反复确认艺人的名字。不知道是不是滤镜开过头,年过半百的王菲更美了,穿白色的服装,搭配出清爽高级的美选白色的服装有很多优势,关注我一段时间的人都会发现,我总是会分享好看的白色服装穿搭,例如帅气的白色服装优雅的白色服装等等,都有高级还年轻的效果。所以当小编看到了王菲的这套白色服装搭Rita女神地位不保?LOL手游昂哥兔女郎cos大获全胜,子琪更给力喜欢看LPL比赛的玩家想必除了对选手比较熟悉之外,对于解说和主持人也都很熟悉的了,其中在LPL拥有女神之称的莫过于混血脸蛋的rita,当年她一上线就吸引大批玩家,不仅声音好听,身型骗取188名老人1800多万元,套路分几步?免费发放米面油吸引老人入会,许以高额返利诱骗老人投资为掩人耳目,出动宝马车接待,组织东南亚旅游,经常农家乐吃喝这些都是针对老年人的诈骗团伙的惯用套路。海口市中级人民法院日前审理了两LOL新英雄虚空女皇符文出装套路全解析12。11版本虚空女皇卑尔维斯(后续称魔鬼鱼)上线,出装和符文都还没有固定的套路,先从技能分析,再逐一分解。先简单分析每个技能的作用,后续作总结。被动技能使用技能后可以增加下2次的江西老板开面馆,面条只卖1元钱,反倒月赚20万,套路给你今天要给大家带来的案例是江西有这么一家面馆,老板利用面条只卖1元钱,不仅不亏,反而在短短一个月的时间里,疯狂盈利20万。1案例背景这家面馆的老板姓胡,而这位胡老板呢,就是今天要给大2018年,金立集团创始人刘立荣,一把输掉7亿美金赌场套路多深再赌最后一把,输赢就这了,再也不赌了。2018年的某一天,塞班岛一家赌场的牌桌上,一个中年男人眼睛通红,他扫了一眼桌上的筹码,想了想,全推了出去。结果呢?后来有记者采访这个男人,男从7000万人到4亿人,清朝出现人口爆炸式增长,什么原因导致的?清朝入关的时候,人口就只有7000万,结果后来清朝灭亡的时候却突然增长到了4亿人,这中间都发生了什么事?又是什么原因导致的?当然,清朝从建立到灭亡一共经过了296年,人口增长也不是金铲铲S7七人口轻松追三星五费,星界龙之徽因过于强势被移除在S7巨龙之巢版本中,星界龙就和S6赛季中的约德尔差不多,可以轻松追三星,因为每刷五次商店,就能出现大量星界龙中的英雄。不过,有所区别的是,约德尔人全员三星之后可以召唤维迦,而星界
杨瀚森1711,中国队却被韩国逆转,李晓勇弃用2人是败笔文水清清北京时间8月26日,U18亚青赛半决赛,杨瀚森延续优异表现,全场轰下17分11篮板3助攻3封盖,球队一度256领先19分,最终却被韩国队8985逆转,李晓勇弃用2人是败笔。抽签结束,除了国际米兰,大家都在笑北京时间8月26日零时,20222023赛季欧洲冠军联赛小组抽签开始,欧洲32路豪强分作8个小组开始向大耳朵杯发起冲击。抽签仪式结束,几家欢喜几家愁,独剩国米在泪流。让我们一起来聊致我们生命中的月光请用一句话概括,幸福是什么在这个世界上,能陪你走到最后,一路不离不弃的,是老伴。睡在自家的床上,吃父母做的饭菜,听爱人给你说情话,跟孩子做游戏。已经算是理想。病,有人照顾冷,有人抱攻略丨座天使兽FM全面解读!恶魔加持灵魂复活,驱散增益生命汲取各位队长好呀歌子又和大家见面啦!游戏内图鉴现已新增座天使兽FM进化路线,座天使兽FM拥有极为强悍的特殊能力,可在战斗中收集敌方目标的灵魂,并为自身提供攻击加成暴击伤害与AP恢复量提科学家发现宇宙中存在着等离子体生命形式地球上有各种各样的生命,包括人类,但生命的形式是否超出我们想象的范围,与我们想象的完全不同?令人惊讶的是,在最先进的科学中,有人指出,可能存在基于等离子体的生命形式,这种等离子体被沉思录15条经典语录,生命因自省而美好沉思录,公元2世纪后期古罗马皇帝马可奥勒留写给自己的书,汇集其思考反省的人生智慧。1。有思考力的人生才是真正光明的人生。我们学会反躬自问,学会与人为友,学会与世和谐,更学会与自己和约人民币28万起,Jeep牧马人20周年版曝光,搭载6。4升V8日前,Auto情报处从相关渠道获得了,Jeep牧马人20周年特别版的官图和相关信息,新车为了纪念牧马人Rubicon20周年打造而来,售价约合人民币28万元起,20周年特别版搭载6世界经济大危机真的要来了吗?任正非说全球经济危机要来了,一部分人信了,正在准备积极过冬一部分人却说,大家不要慌,华为公司这是因为被制裁了,你看他哪年不说要过冬。我只想说,后面这一部分人,不是蠢就是坏。大家都知一旦发生经济大萧条,最保值的是房子还是黄金?头条创作挑战赛一般来说,在经济衰退和经济大萧条之前,家庭债务都会出现大规模的增长。家庭债务从何而来呢?一部分是消费债务,一份是房地产按揭贷款债务,当债务过重影响到经济,或是经济不好融资丨巨翊科技完成超亿元人民币B轮融资,中金资本旗下基金领投创业邦从媒体获悉,近日,巨翊科技(上海)有限公司(以下简称巨翊科技)宣布完成超亿元人民币B轮融资,本轮融资由中金资本旗下基金领投,现有股东君联资本元生创投联合跟投。本轮融资将助力巨苏州对购房资格再做调整,非本地户籍购房仍需6个月社保距全域放开对非本地户籍购买首套房资格仅一天,苏州主城多区恢复了此前的购房政策要求。9月16日,第一财经从苏州市相城区房产交易中心相关工作人员处了解到,非本市户籍购房仍需缴纳6个月社
友情链接:快好找快生活快百科快传网中准网文好找聚热点快软网