Java协程要来了--Java 19 Virtual Threads

2,439 阅读6分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第3天,点击查看活动详情

日期更新说明
2022年6月8日初版编辑

前言

最近在参加掘金征文挑战,想想之前输入、输出计划,想想的确,通过输出文章锻炼一下自己总结能力和学习能力也是不错的;就是写文章真的很耗费时间,可能一篇文章您几分钟看完,但是可能在写的时候启示,需要花费几个小时的时间,如果是精品或者不是简单知识点罗列,那么花费的时间将会成倍增加,但是如果阅读中你感受到新的收获和心得,那么也是不错的,同时希望你可以点赞支持。

Go语言的协程的高效和轻量级,使得Go在云原生时代受到不少人的喜爱;随着JEP 425 虚拟线程(预览版)的提议的到来,Java的“协程”也是慢慢来了;让我给介绍一下吧。

简介

快速预览

下面是国外大佬对于“Virtual Threads”的介绍(希望掘金支持视屏);学习还得是原汁原味东西才好。

YouTube链接:Java 19 Virtual Threads - JEP Café #11

player.bilibili.com/player.html…

其中原文大概观点如下总结(对于没有流量的同学)

  • JEP 425 虚拟线程(预览版)最为JEP提案,大概率会在后续版本中出现;但现阶段投入生产还需慎重考虑
  • Virtual Threads优势在基于JDK实现的轻量级“线程”和传统依赖操作系统的线程来说,在启动时间、CPU和内存占用方面更有效的;更少的占用和自动挂起(在阻塞这方面是有优势的)。
  • 建议使用Virtual Threads时,不建议使用池化

Virtual Threads

定义

虚拟线程,我们来看看是什么鬼:

A virtual thread is an instance of that is not tied to a particular OS thread. A platform thread, by contrast, is an instance of implemented in the traditional way, as a thin wrapper around an OS thread.java.lang.Threadjava.lang.Thread

翻译:

虚拟线程是在基础操作系统线程上运行 Java 代码的实例,但在代码的整个生存期内不捕获操作系统线程。这意味着许多虚拟线程可以在同一操作系统线程上运行其 Java 代码,从而有效地共享它。虽然平台线程垄断了宝贵的操作系统线程,但虚拟线程却没有。虚拟线程数可以比 OS 线程数大得多。java.lang.Thread虚拟线程是由 JDK 而不是操作系统提供的线程的轻量级实现。它们是用户模式线程的一种形式,在其他多线程语言中已经成功(例如,Go中的goroutines和Erlang中的processs)。用户模式线程甚至在Java的早期版本中被称为所谓的“绿色线程”,当时操作系统线程尚未成熟和广泛。然而,Java的绿色线程都共享一个操作系统线程(M:1调度),并最终被平台线程超越,平台线程作为OS线程的包装器实现(1:1调度)。虚拟线程采用 M:N 调度,其中大量 (M) 个虚拟线程被调度为在较少数量的 (N) 个 OS 线程上运行。

简单说:轻量级+用户态+共享操作系统资源

优势

了解Go的“协程”;其实其优势不言而喻。

传统线程的由于系统资源优先,使用必须使用池化思想,避免资源浪费;创建线程变得容易,并有助于提高应用程序吞吐量。如今,服务器应用程序通过将并发用户请求委托给独立的执行单元(即平台线程)来处理并发用户请求。这种按请求线程编程风格易于理解、易于编程、易于调试和分析。但是,平台线程的数量有限,因为 JDK 将线程实现为围绕 OS 线程的包装器。因此,当应用程序需要扩展以增加吞吐量时,它无法使用平台线程进行扩展。现在,由于大量虚拟线程很容易创建,因此按请求线程编程样式可以缓解这种可伸缩性瓶颈。

虚拟线程支持线程局部变量、同步块、线程中断等。对于Java开发人员来说,这意味着虚拟线程只是廉价而丰富的线程,并且编程方法根本不会改变。针对经典线程编写的现有 Java 代码无需更改即可在虚拟线程中轻松运行。

  • 节省内存,每个线程需要分配一段栈内存,以及内核里的一些资源
  • 节省分配线程的开销(创建和销毁线程要各做一次syscall)
  • 节省大量线程切换带来的开销

使用

下载 JDK 19 的早期访问版本并熟悉它。该类提供了一个新方法,该方法采用 Runnable 类型的参数并创建虚拟线程。请考虑以下示例:Thread startVirtualThread(Runnable)

public class Main {
    public static void main(String[] args) throws InterruptedException {
        var vThread = Thread.startVirtualThread(() -> {
            System.out.println("Hello from the virtual thread");
        });

        vThread.join();
    }
}

由于这是预览功能,因此开发人员需要提供该标志来编译此代码,如以下命令所示:--enable-preview

javac --release 19 --enable-preview Main.java

运行程序还需要相同的标志:

java --enable-preview Main

但是,可以使用源代码启动器直接运行它。在这种情况下,命令行将是:

java --source 19 --enable-preview Main.java

线程方面:

永远不应池化虚拟线程,因为每个虚拟线程在其生存期内只能运行一个任务。相反,模型是无约束地创建虚拟线程。为此,添加了一个未绑定的执行程序。可以通过新的工厂方法访问它。这些方法支持与使用线程池和 的现有代码的迁移和互操作性。请考虑以下示例:Executors.newVirtualThreadPerTaskExecutor()ExecutorService

import java.time.Duration;
import java.util.concurrent.Executors;
import java.util.stream.IntStream;

public class Main {
    public static void main(String[] args) {
        
        try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
            IntStream.range(0, 10_000).forEach(i -> {
                executor.submit(() -> {
                    Thread.sleep(Duration.ofSeconds(1));
                    return i;
                });
            });
        }
    }
}

总结思考

Java新的特性,在并发这块带来新的改动,应该是巨大的改变,无论语言特性上和Go、kotlin等看齐,它给并发带来更过的活力和可能性。Java在使用API上也是尽量保持使用习惯的兼容,这点也是很重,但是对于协程通信,已经三方架构支持方面更近,都需要时间。对于Javaer来说,应该是不错的期待。