java中协程使用

java中协程使用

背景

Java协程,大家做Java这么久估计也没有怎么听过Java协程的东西,但是经常有听到协程的概念。本片文章就java中具体使用协程给大家介绍下协程具体在java中的使用方式。在这里插入图片描述

看了很多java应用quasar框架实现协程的例子,但是很多写的驴头不对马嘴,所以这里自己写篇文章记录下。

当我们在使用多线程的时候,如果存在长时间的I/O操作。这个时候线程一直处于阻塞状态,如果线程很多的时候,会存在很多线程处于空闲状态,造成了资源应用不彻底。相对的协程不一样了,在单线程中多个任务来回自行如果出现长时间的I/O操作,让其让出目前的协程调度,执行下一个任务。当然可能所有任务,全部卡在同一个点上,但是这只是针对于单线程而言,当所有数据正常返回时,会同时处理当前的I/O操作。

实践

java使用协程实例
第一步:maven项目中加入quasar依赖

		<dependency>
            <groupId>co.paralleluniverse</groupId>
            <artifactId>quasar-core</artifactId>
            <version>0.7.9</version>
            <classifier>jdk8</classifier>
        </dependency>

第二步:使用quasar框架实现协程
完整代码如下:

import co.paralleluniverse.fibers.Fiber;
import co.paralleluniverse.strands.SuspendableCallable;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.LinkedBlockingQueue;

@Slf4j
public class QuasarTest {

    public static void start() throws Exception {
        //使用阻塞队列来获取结果。
        LinkedBlockingQueue<Fiber<Integer>> fiberQueue = new LinkedBlockingQueue<>();
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        for (int i = 0; i < 10; i++) {
            int finalI = i;
            //这里的Fiber有点像Callable,可以返回数据
            Fiber<Integer> fiber = new Fiber<>((SuspendableCallable<Integer>) () -> {
                //这里用于测试内存占用量
                log.debug("in-" + finalI );
                Fiber.sleep(10000);
                return finalI;
            });
            //开始执行
            fiber.start();
            //加入队列
            fiberQueue.add(fiber);
        }
        while (true) {
            //阻塞
            Fiber<Integer> fiber = fiberQueue.take();
            log.debug("out-" + fiber.get() );
        }
    }
}

执行结果如下:

2022-03-02 17:28:50.327 DEBUG 13832 --- [r-pool-worker-2] com.ai.toptea.sysm.util.QuasarTest       : in-1
2022-03-02 17:28:50.327 DEBUG 13832 --- [r-pool-worker-9] com.ai.toptea.sysm.util.QuasarTest       : in-0
2022-03-02 17:28:50.327 DEBUG 13832 --- [r-pool-worker-4] com.ai.toptea.sysm.util.QuasarTest       : in-3
2022-03-02 17:28:50.327 DEBUG 13832 --- [-pool-worker-11] com.ai.toptea.sysm.util.QuasarTest       : in-2
2022-03-02 17:28:50.329 DEBUG 13832 --- [-pool-worker-13] com.ai.toptea.sysm.util.QuasarTest       : in-4
2022-03-02 17:28:50.329 DEBUG 13832 --- [r-pool-worker-6] com.ai.toptea.sysm.util.QuasarTest       : in-5
2022-03-02 17:28:50.329 DEBUG 13832 --- [r-pool-worker-8] com.ai.toptea.sysm.util.QuasarTest       : in-7
2022-03-02 17:28:50.329 DEBUG 13832 --- [-pool-worker-15] com.ai.toptea.sysm.util.QuasarTest       : in-6
2022-03-02 17:28:50.330 DEBUG 13832 --- [-pool-worker-10] com.ai.toptea.sysm.util.QuasarTest       : in-9
2022-03-02 17:28:50.330 DEBUG 13832 --- [r-pool-worker-1] com.ai.toptea.sysm.util.QuasarTest       : in-8
2022-03-02 17:29:00.337 DEBUG 13832 --- [r-pool-worker-8] com.ai.toptea.sysm.util.QuasarTest       : in-4
2022-03-02 17:29:00.337 DEBUG 13832 --- [-pool-worker-15] com.ai.toptea.sysm.util.QuasarTest       : in-5
2022-03-02 17:29:00.337 DEBUG 13832 --- [-pool-worker-13] com.ai.toptea.sysm.util.QuasarTest       : in-2
2022-03-02 17:29:00.337 DEBUG 13832 --- [r-pool-worker-6] com.ai.toptea.sysm.util.QuasarTest       : in-7
2022-03-02 17:29:00.337 DEBUG 13832 --- [r-pool-worker-2] com.ai.toptea.sysm.util.QuasarTest       : in-1
2022-03-02 17:29:00.337 DEBUG 13832 --- [r-pool-worker-4] com.ai.toptea.sysm.util.QuasarTest       : in-3
2022-03-02 17:29:00.337 DEBUG 13832 --- [-pool-worker-10] com.ai.toptea.sysm.util.QuasarTest       : in-0
2022-03-02 17:29:00.337 DEBUG 13832 --- [r-pool-worker-1] com.ai.toptea.sysm.util.QuasarTest       : in-6
2022-03-02 17:29:00.338 DEBUG 13832 --- [r-pool-worker-3] com.ai.toptea.sysm.util.QuasarTest       : in-9
2022-03-02 17:29:00.338 DEBUG 13832 --- [-pool-worker-12] com.ai.toptea.sysm.util.QuasarTest       : in-8
2022-03-02 17:29:00.339 DEBUG 13832 --- [  restartedMain] com.ai.toptea.sysm.util.QuasarTest       : out-0
2022-03-02 17:29:00.339 DEBUG 13832 --- [  restartedMain] com.ai.toptea.sysm.util.QuasarTest       : out-1
2022-03-02 17:29:00.339 DEBUG 13832 --- [  restartedMain] com.ai.toptea.sysm.util.QuasarTest       : out-2
2022-03-02 17:29:00.339 DEBUG 13832 --- [  restartedMain] com.ai.toptea.sysm.util.QuasarTest       : out-3
2022-03-02 17:29:00.339 DEBUG 13832 --- [  restartedMain] com.ai.toptea.sysm.util.QuasarTest       : out-4
2022-03-02 17:29:00.339 DEBUG 13832 --- [  restartedMain] com.ai.toptea.sysm.util.QuasarTest       : out-5
2022-03-02 17:29:00.339 DEBUG 13832 --- [  restartedMain] com.ai.toptea.sysm.util.QuasarTest       : out-6
2022-03-02 17:29:00.339 DEBUG 13832 --- [  restartedMain] com.ai.toptea.sysm.util.QuasarTest       : out-7
2022-03-02 17:29:00.339 DEBUG 13832 --- [  restartedMain] com.ai.toptea.sysm.util.QuasarTest       : out-8
2022-03-02 17:29:00.339 DEBUG 13832 --- [  restartedMain] com.ai.toptea.sysm.util.QuasarTest       : out-9

这里可以看到log.debug("in-" + finalI );被执行了两次,后面分析原因是Fiber.sleep(10000);干扰了协程执行顺序,去掉即可。
同时需要注意:在使用协程是不可使用Thread.sleep,因为直接操作线程会让协程整体调度出现问题。

结论

从打印时间上可以看出10个协程没有被Fiber.sleep(10000);阻塞,而是记录下当前协程的栈信息后,再调度其他协程执行,所以执行时间都是17:28:50。

这里讲解下:
Quasar里的Fiber其实是一个continuation,他可以被Quasar定义的scheduler调度,一个continuation记录着运行实例的状态,而且会被随时中断,并且也会随后在他被中断的地方恢复。Quasar其实是通过修改bytecode来达到这个目的,所以运行Quasar程序的时候,你需要先通过java-agent在运行时修改你的代码,当然也可以在编译期间这么干。golang的内置了自己的调度器,Quasar则默认使用ForkJoinPool这个JDK7以后才有的,具有work-stealing功能的线程池来当调度器。work-stealing非常重要,因为你不清楚哪个Fiber会先执行完,而work-stealing可以动态的从其他的等等队列偷一个context过来,这样可以最大化使用CPU资源。

从打印日志中r-pool-worker-2可以看出work-stealing中的队列的队列号不同,说明是在不同协程中执行的命令。

扩展

1、Quasar里的Fiber其实是一个continuation,他可以被Quasar定义的scheduler调度,一个continuation记录着运行实例的状态,而且会被随时中断,并且也会随后在他被中断的地方恢复。Quasar其实是通过修改bytecode来达到这个目的,所以运行Quasar程序的时候,你需要先通过java-agent在运行时修改你的代码,当然也可以在编译期间这么干。golang的内置了自己的调度器,Quasar则默认使用ForkJoinPool这个JDK7以后才有的,具有work-stealing功能的线程池来当调度器。work-stealing非常重要,因为你不清楚哪个Fiber会先执行完,而work-stealing可以动态的从其他的等等队列偷一个context过来,这样可以最大化使用CPU资源。

2、那这里你会问了,Quasar怎么知道修改哪些字节码呢,其实也很简单,Quasar会通过java-agent在运行时扫描哪些方法是可以中断的,同时会在方法被调用前和调度后的方法内插入一些continuation逻辑,如果你在方法上定义了@Suspendable注解,那Quasar会对调用该注解的方法做类似下面的事情。

3、这里假设你在方法f上定义了@Suspendable,同时去调用了有同样注解的方法g,那么所有调用f的方法会插入一些字节码,这些字节码的逻辑就是记录当前Fiber栈上的状态,以便在未来可以动态的恢复。(Fiber类似线程也有自己的栈)。在suspendable方法链内Fiber的父类会调用Fiber.park,这样会抛出SuspendExecution异常,从而来停止线程的运行,好让Quasar的调度器执行调度。这里的SuspendExecution会被Fiber自己捕获,业务层面上不应该捕获到。如果Fiber被唤醒了(调度器层面会去调用Fiber.unpark),那么f会在被中断的地方重新被调用(这里Fiber会知道自己在哪里被中断),同时会把g的调用结果(g会return结果)插入到f的恢复点,这样看上去就好像g的return是f的local variables了,从而避免了callback嵌套。

4、上面啰嗦了一大堆,其实简单点讲就是,想办法让运行中的线程栈停下来,好让Quasar的调度器介入。JVM线程中断的条件只有两个,一个是抛异常,另外一个就是return。这里Quasar就是通过抛异常的方式来达到的,所以你会看到我上面的代码会抛出SuspendExecution。但是如果你真捕获到这个异常,那就说明有问题了,所以一般会这么写。

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值