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中的Runnable
和Callable
的区别了。其余参数都可以省略,name
为协程的名称,scheduler
是调度器,默认使用FiberForkJoinScheduler
,stackSize
指定用于保存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
运行后时间只有使用线程池时的一半多一点,确实能大大缩短程序的效率。