当前位置: 首页 > 科技观察

技术和努力?JDK19中的虚拟线程是什么鬼?

时间:2023-03-20 13:11:12 科技观察

最近,JDK19发布了,引入了几个新特性,其中一个更值得关注的是虚拟线程的添加。很多人可能会疑惑,什么是虚拟线程,和我们现在用的平台线程有什么区别?要弄清楚JDK19中的虚拟线程,首先要了解线程是如何实现的。我们都知道线程是如何实现的。在操作系统中,线程是比进程更轻量级的调度执行单元。线程的引入可以将一个进程的资源分配和执行调度分开。每个线程可以共享进程资源并且可以独立调度。实际上,线程的实现方式主要有三种:使用内核线程、使用用户线程、用户线程与轻量级进程结合使用。使用内核线程来实现内核线程(Kernel-LevelThread,KLT)是操作系统内核(Kernel,以下简称内核)直接支持的线程。线程是被调度的,负责将线??程的任务映射到各个处理器,并为应用程序提供API接口来管理线程。应用程序一般不会直接使用内核线程,而是使用内核线程的一个高层接口——LightWeightProcess(LWP),也就是我们通常所说的线程。由于每个轻量级进程都由一个内核线程支持,因此只有首先支持内核线程才能有轻量级进程。在内核线程的支持下,每个轻量级进程成为一个独立的调度单元。即使一个轻量级进程阻塞在一个系统调用中,也不会影响整个进程继续工作。但是轻量级进程有其局限性:首先,由于它是基于内核线程实现的,各种线程操作,如创建、销毁、同步等,都需要系统调用。系统调用的成本比较高,需要在用户态(UserMode)和内核态(KernelMode)之间来回切换。其次,每个轻量级进程都需要一个内核线程来支持,所以轻量级进程会消耗一定的内核资源(比如内核线程的栈空间),所以一个系统支持的轻量级进程的数量是有限的。使用用户线程实现用户空间线程库的建立,通过运行时系统(Run-timeSystem)完成线程管理,因为这个线程的实现是在用户空间,所以操作系统的内核并不知道线程的存在,所以内核管理进程,所以这个线程的切换不需要内核操作。在这个实现中,进程和线程之间是一对多的关系。这种线程实现方式的优点是线程切换快,可以运行在任何操作系统上,只需要实现线程库即可。但是缺点也很明显,就是所有的线程操作都需要用户程序自己来处理,而且由于大部分系统调用都是阻塞的,一旦一个进程被阻塞,该进程中的所有线程也会被阻塞。另外,在多处理器系统中如何将线程映射到其他处理器也是一个比较大的问题。还有一种使用用户线程和轻量级进程的混合实现方式,即线程的创建是通过线程库在用户空间完成的,而线程的调度是由内核来完成的。多个用户线程复用多个内核线程。这里不会展开Java线程的实现。以上是操作系统中实现线程的三种方式。不同的操作系统在实现线程时会使用不同的机制。例如,Windows使用内核线程,而Solaris是通过混合模式实现的。Java作为一种跨平台的编程语言,其线程的实现实际上依赖于特定的操作系统。对于比较常用的windows和linux,都是以内核线程的形式实现的。也就是说,当我们在JAVA代码中创建一个Tread时,其实需要映射到操作系统线程的具体实现上,因为内核线程的普通实现需要内核参与创建和调度。因此,成本相对较高。虽然JAVA提供了线程池的方式来避免重复创建线程,但是仍然有很大的优化空间。而且,这种实现方式意味着受机器资源的影响,平台线程的数量也是有限的。虚拟线程JDK19引入的虚拟线程是JDK实现的轻量级线程,可以避免上下文切换带来的额外开销。他的实现原理是JDK不再让每个线程一一对应一个操作系统线程,而是将多个虚拟线程映射到少数操作系统线程,通过有效的调度避免那些上下文。转变。此外,我们可以在应用程序中创建大量的虚拟线程,而与平台线程的数量无关。这些虚拟线程由JVM管理,因此它们不会增加额外的上下文切换开销,因为它们作为普通Java对象存储在RAM中。虚拟线程和平台线程的区别首先,虚拟线程总是守护线程。setDaemon(false)方法无法将虚拟线程更改为非守护线程。因此,重要的是要注意,当所有启动的非守护线程都终止时,JVM将终止。这意味着JVM在退出之前不会等待虚拟线程完成。其次,即使使用setPriority()方法,虚拟线程始终具有正常的优先级并且无法更改。在虚拟线程上调用此方法没有任何效果。此外,虚拟线程不支持stop()、suspend()或resume()等方法。这些方法在虚拟线程上调用时抛出UnsupportedOperationException。如何使用虚拟线程接下来介绍一下JDK19中如何使用虚拟线程,首先可以通过Thread.startVirtualThread()来运行一个虚拟线程:Thread.startVirtualThread(()->{System.out.println("虚拟线程执行...");});二、通过Thread.Builder也可以创建虚拟线程,Thread类提供ofPlatform()创建平台线程,ofVirtual()创建虚拟场景。Thread.BuilderplatformBuilder=Thread.ofPlatform().name("平台线程");Thread.BuildervirtualBuilder=Thread.ofVirtual().name("虚拟线程");Threadt1=platformBuilder.start(()->{...});线程t2=virtualBuilder.start(()->{...});另外,线程池还支持虚拟线程,可以通过Executors.newVirtualThreadPerTaskExecutor()创建:.submit(()->{Thread.sleep(Duration.ofSeconds(1));returni;});});}但是不建议使用带线程池的虚拟线程,因为Java线程池是旨在避免创建新的操作系统线程的开销,但是创建虚拟线程的开销并不大,因此没有必要进入线程池。性能差异说了半天,虚拟线程能不能提升性能,提升多少?让我们做一个测试。让我们写一个简单的任务,等待1秒然后在控制台打印消息:finalAtomicIntegeratomicInteger=newAtomicInteger();Runnablerunnable=()->{}catch(Exceptione){System.out.println(e);}System.out.println("工作完成-"+atomicInteger.incrementAndGet());};现在,我们将从这个Runnable中创建10,000个线程,并使用虚拟线程和平台线程来执行它们,以比较两者的性能。先从我们比较熟悉的平台线程的实现说起:Instantstart=Instant.now();try(varexecutor=Executors.newFixedThreadPool(100)){for(inti=0;i<10_000;i++){executor.submit(可运行);}}Instantfinish=Instant.now();longtimeElapsed=Duration.between(start,finish).toMillis();System.out.println("总耗时:"+timeElapsed);输出结果为:Totaltime-consumed:102323总耗时约100秒。接下来用虚拟线程运行看看,因为在JDK19中,虚拟线程是一个预览API,默认是禁用的。所以需要使用$java——source19——enable-previewxx.java来运行代码。即时启动=Instant.now();try(varexecutor=Executors.newVirtualThreadPerTaskExecutor()){for(inti=0;i<10_000;i++){executor.submit(runnable);}}即时完成=即时。now();longtimeElapsed=Duration.between(start,finish).toMillis();System.out.println("总耗时:"+timeElapsed);使用Executors.newVirtualThreadPerTaskExecutor()创建一个虚拟线程,执行结果如下:总耗时:1674总耗时约1.6秒。100秒和1.6秒的差距足以看出虚拟线程的性能提升还是立竿见影的。总结本文介绍了JDK19引入的新的虚拟线程,即协程,主要是为了解决读取操作系统中的线程需要依赖内核线程的实现,导致大量额外开销的问题。通过在Java语言层面引入虚拟线程,通过JVM进行调度管理,从而降低上下文切换的成本。同时,经过简单的demo测试,我们发现虚拟线程的执行效率确实高了很多。但是在使用的时候也需要注意。虚拟线程是一个守护线程,所以它可能会在虚拟机执行完之前关闭它。参考资料:https://openjdk.org/jeps/425https://howtodoinjava.com/java/multi-threading/virtual-threads/