Java 协程 Quasar

线程在阻塞状态和可运行状态的切换,以及线程间的上下文切换都会造成性能的损耗。为了解决这些问题,引入协程coroutine这一概念,就像在一个进程中允许存在多个线程,在一个线程中,也可以存在多个协程。

 

使用协程究竟有什么好处呢?

首先,执行效率高。线程的切换由操作系统内核执行,消耗资源较多。而协程由程序控制,在用户态执行,不需要从用户态切换到内核态,我们也可以理解为,协程是一种进程自身来调度任务的调度模式,因此协程间的切换开销远小于线程切换。

其次,节省资源。因为协程在本质上是通过分时复用了一个单线程,因此能够节省一定的资源。

 

虽然在Java官方的jdk中不能直接使用协程,但是,有其他的开源框架借助动态修改字节码的方式实现了协程,比如Quasar。

 

<dependency>
    <groupId>co.paralleluniverse</groupId>
    <artifactId>quasar-core</artifactId>
    <version>0.7.10</version>
</dependency>

  

下面我们模拟一个简单的场景,假设我们有一个任务,平均执行时间为1秒,分别测试一下使用线程和协程并发执行10000次需要消耗多少时间。

先通过线程进行调用,直接使用Executors线程池:

public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(10000);
        long start = System.currentTimeMillis();
        ExecutorService executor = Executors.newCachedThreadPool();
        for (int i = 0; i < 10000; i++) {
            executor.submit(() -> {
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        long end = System.currentTimeMillis();
        System.out.println("Thread use:" + (end - start) + " ms");
    }

  

下面我们再用Quasar中的协程跑一下和上面相同的流程。这里我们要使用的是Quasar中的Fiber,它可以被翻译为协程纤程,创建Fiber的类型主要可分为下面两类:

public Fiber(String name, FiberScheduler scheduler, int stackSize, SuspendableRunnable target);
public Fiber(String name, FiberScheduler scheduler, int stackSize, SuspendableCallable<V> target);

Fiber中可以运行无返回值的SuspendableRunnable或有返回值的SuspendableCallable,看这个名字也知道区别就是java中的RunnableCallable的区别了。其余参数都可以省略,name为协程的名称,scheduler是调度器,默认使用FiberForkJoinSchedulerstackSize指定用于保存fiber调用栈信息的stack大小。

在下面的代码中,使用了Fiber.sleep()方法进行协程的休眠,和Thread.sleep()非常类似。

 

public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch=new CountDownLatch(10000);
        long start = System.currentTimeMillis();

        for (int i = 0; i < 10000; i++) {
            new Fiber<>(new SuspendableRunnable(){
                @Override
                public void run() throws SuspendExecution, InterruptedException {
                    Fiber.sleep(1000);
                    countDownLatch.countDown();
                }
            }).start();
        }

        countDownLatch.await();
        long end = System.currentTimeMillis();
        System.out.println("Fiber use:"+(end-start)+" ms");
    }

直接运行,报了一个警告:

QUASAR WARNING: Quasar Java Agent isn't running. If you're using another instrumentation method you can ignore this message; otherwise, please refer to the Getting Started section in the Quasar documentation.

Quasar生效的原理是基于Java instrument技术吗,所以这里需要给它添加一个代理Agent。找到本地maven仓库中已经下好的jar包,在VM options中添加参数:

-javaagent:C:\Users\tl19638\.m2\repository\co\paralleluniverse\quasar-core\0.7.10\quasar-core-0.7.10.jar

  运行后时间只有使用线程池时的一半多一点,确实能大大缩短程序的效率。