zoukankan      html  css  js  c++  java
  • Java协程编程之Loom项目尝鲜

    前提

    之前很长一段时间关注JDK协程库的开发进度,但是前一段时间比较忙很少去查看OpenJDK官网的内容。Java协程项目Loom(因为项目还在开发阶段,OpenJDK给出的官网https://openjdk.java.net/projects/loom中只有少量Loom项目相关的信息)已经在2018年之前立项,目前已经发布过基于JDK17编译和JDK18编译等早期版本,笔者在下载Loom早期版本的时候只找到JDK18编译的版本:

    下载入口在:https://jdk.java.net/loom

    由于该JDK版本过高,目前可以使用主流IDE导入Loom-JDK-18+9进行代码高亮和语法提醒,暂时找不到方法进行编译,暂时使用该JDK执行目录下的的javac命令脚本进行编译,使用java命令脚本运行。

    Loom项目简单介绍

    Loom - Fibers, Continuations and Tail-Calls for the JVM

    Loom项目的标题已经凸显了引入的三大新特性:

    • Fibers:几年前看过当时的Loom项目的测试代码就是使用Fiber这个API(现在这个API已经被移除),意为轻量级线程,即协程,又称为轻量级用户线程,很神奇的是在目前的JDK中实际上称为Virtual Thread虚拟线程
    • Continuations:直译为"连续",实现上有点像闭包,参考不少资料,尚未准确理解其具体含义,感觉可以"粗暴"解读为"程序接下来要执行什么"或者"下一个要执行的代码块"
    • Tail-Calls:尾调用VM级别支持

    三个新特性不详细展开,目前只是EA版本,还存在修改的可能性,所以也没必要详细展开。

    Virtual Thread使用

    当前版本Loom项目中协程使用并没有引入一个新的公开的虚拟线程VirtualThread类,虽然真的存在VirtualThread,但这个类使用default修饰符,隐藏在java.lang包中,并且VirtualThreadThread的子类。协程的创建API位于Thread类中:

    使用此API创建协程如下:

    public static void main(String[] args) {
        Thread fiber = Thread.startVirtualThread(() -> System.out.println("Hello Fiber"));
    }
    

    从当前的源码可知:

    • VirtualThread会通过Thread.currentThread()获取父线程的调度器,如果在main方法运行,那么上面代码中的协程实例的父线程就是main线程
    • 默认的调度器为系统创建的ForkJoinPool实例(VirtualThread.DEFAULT_SCHEDULER),输入的Runnable实例会被封装为RunContinuation,最终由调度器执行
    • 对于timed unpark(正在阻塞,等待唤醒)的协程,使用系统创建的ScheduledExecutorService实例进行唤醒
    • 这个静态工厂方法创建完协程马上运行,返回的是协程实例

    如果按照上面的Thread.startVirtualThread()方法去创建协程,显然无法定义协程的名称等属性。Loom项目为Thread类引入了建造者模式,比较合理地解决了这个问题:

    // 创建平台线程建造器,对应于Thread实例
    public static Builder.OfPlatform ofPlatform() {
        return new ThreadBuilders.PlatformThreadBuilder();
    }
    
    // 创建虚拟线程建造器,对应于VirtualThread
    public static Builder.OfVirtual ofVirtual() {
        return new ThreadBuilders.VirtualThreadBuilder();
    }
    

    简单说就是:

    • ofPlatform()方法用于构建Thread实例,这里的Platform Thread(平台线程)其实就是JDK1.0引入的线程实例,普通的用户线程
    • ofVirtual()方法用于构建VirtualThread实例,也就是构建协程实例

    这两个建造器实例的所有Setter方法链展开如下:

    public static void main(String[] args) {
        Thread.Builder.OfPlatform platformThreadBuilder = Thread.ofPlatform()
                // 是否守护线程
                .daemon(true)
                // 线程组
                .group(Thread.currentThread().getThreadGroup())
                // 线程名称
                .name("thread-1")
                // 线程名称前缀 + 起始自增数字 => prefix + start,下一个创建的线程名称就是prefix + (start + 1)
                // start > 0的情况下会覆盖name属性配置
                .name("thread-", 1L)
                // 是否启用ThreadLocal
                .allowSetThreadLocals(false)
                // 是否启用InheritableThreadLocal
                .inheritInheritableThreadLocals(false)
                // 设置优先级
                .priority(100)
                // 设置线程栈深度
                .stackSize(10)
                // 设置未捕获异常处理器
                .uncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
                    @Override
                    public void uncaughtException(Thread t, Throwable e) {
    
                    }
                });
        // thread-1
        Thread firstThread = platformThreadBuilder.unstarted(() -> System.out.println("Hello Platform Thread First"));
        // thread-2
        Thread secondThread = platformThreadBuilder.unstarted(() -> System.out.println("Hello Platform Thread Second"));
        Thread.Builder.OfVirtual virtualThreadBuilder = Thread.ofVirtual()
                // 协程名称
                .name("fiber-1")
                // 协程名称前缀 + 起始自增数字 => prefix + start,下一个创建的协程名称就是prefix + (start + 1)
                // start > 0的情况下会覆盖name属性配置
                .name("fiber-", 1L)
                // 是否启用ThreadLocal
                .allowSetThreadLocals(false)
                // 是否启用InheritableThreadLocal
                .inheritInheritableThreadLocals(false)
                // 设置调度器,Executor实例,也就是调度器是一个线程池,设置为NULL会使用VirtualThread.DEFAULT_SCHEDULER
                .scheduler(null)
                // 设置未捕获异常处理器
                .uncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
                    @Override
                    public void uncaughtException(Thread t, Throwable e) {
    
                    }
                });
        // fiber-1
        Thread firstFiber = virtualThreadBuilder.unstarted(() -> System.out.println("Hello Platform Virtual First"));
        // fiber-2
        Thread secondFiber = virtualThreadBuilder.unstarted(() -> System.out.println("Hello Platform Virtual Second"));
    }
    

    这里可以发现一点,就是建造器是可以复用的。如果想用建造器创建同一批参数设置相同的线程或者协程,可以设置name(String prefix, long start)方法,定义线程或者协程的名称前缀和一个大于等于0的数字,反复调用Builder#unstarted(Runnable task)方法就能批量创建线程或者协程,名称就设置为prefix + startprefix + (start + 1)prefix + (start + 2)以此类推。协程创建基本就是这么简单,运行的话直接调用start()方法:

    public class FiberSample2 {
    
        public static void main(String[] args) throws Exception {
            Thread.ofVirtual()
                    .name("fiber-1")
                    .allowSetThreadLocals(false)
                    .inheritInheritableThreadLocals(false)
                    .unstarted(() -> {
                        Thread fiber = Thread.currentThread();
                        System.out.printf("[%s,daemon:%s,virtual:%s] - Hello World
    ", fiber.getName(),
                                fiber.isDaemon(), fiber.isVirtual());
                    }).start();
            // 主线程休眠
            Thread.sleep(Long.MAX_VALUE);
        }
    }
    

    目前无法在主流IDE编译上面的类,所以只能使用该JDK目录下的工具编译和运行,具体如下:

    # 执行 - 当前目录I:J-Projectsframework-source-codefiber-samplesrcmainjava
    (1)编译:I:EnvironmentJavajdk-18-loominjavac.exe I:J-Projectsframework-source-codefiber-samplesrcmainjavacn	hrowxfibersampleFiberSample2.java
    (2)执行main方法:I:EnvironmentJavajdk-18-loominjava.exe  cn.throwx.fiber.sample.FiberSample2
    

    这里也看出了一点,所有的协程实例的daemon标识默认为true且不能修改。

    小结

    如果用尝鲜的角度去使用Loom项目,可以提前窥探JVM开发者们是如何基于协程这个重大特性进行开发的,这对于提高学习JDK内核代码的兴趣有不少帮助。从目前来看,对于协程的实现Loom项目距离RELEASE版本估计还有不少功能需要完善,包括新增API的稳定性,以及协程是否能够移植到原有的JUC类库中使用(当前的Loom-JDK-18+9没有对原来的线程池等类库进行修改)等问题需要解决,所以在保持关注的过程中静心等待吧。

    (e-a-20210818 c-2-d)

  • 相关阅读:
    深度学习面试题27:非对称卷积(Asymmetric Convolutions)
    深度学习面试题26:GoogLeNet(Inception V2)
    深度学习面试题25:分离卷积(separable卷积)
    深度学习面试题24:在每个深度上分别卷积(depthwise卷积)
    深度学习面试题23:批次张量和卷积核的简易定义方式
    深度学习面试题22:批量归一化在实践中的应用
    深度学习面试题21:批量归一化(Batch Normalization,BN)
    Docker安装ElasticSearch 版本7.1.1
    Go语言入门——hello world
    eclipse下配置GO语言的开发环境
  • 原文地址:https://www.cnblogs.com/throwable/p/15164665.html
Copyright © 2011-2022 走看看