1. 什么是协程
简单可以理解为理解为用户态线程,在学过操作系统的时候我们知道,线程可以在用户态和内核态之间切换,目前java中所实现的线程都会有自己的上下文,上下文在线程的运行过程中会不断的切换,线程就会不断地从用户态和内核态之间切换。由此带来巨大开销。
协程:我们可以简单的理解为用户态线程,避免了内核态和用户态之间的切换来减少线程上下文切换的开销。
但是JVM原生是不支持这样的操作的。因此如果要在纯java代码里需要使用协程的话需要引入第三方包,如kilim,Quasar。而kilim已经是很久未更新了,接下来我们使用Quasar。
2. Quasar
为了榨干CPU的性能,使得一个资源利用率最大化,在java种引进了fork/jion机制。
fork/jion机制可以参考:https://blog.csdn.net/qq_35688140/article/details/100769876
因为有些fiber可能先执行完成,而work-stealing可以动态的从其他的等等队列偷一个context过来,这样可以最大化使用CPU资源。
3. 关于线程和协程的性能对比
因为Quasar本身并没有提供maven库,这就使得直接使用Quasar需要手动编译安装等等。我们这里直接使用阿里巴巴镜像源下可以直接使用Quasar的maven库。(需要配置maven镜像源为阿里镜像源才可以生效)
pom.xml
<dependency>
<groupId>co.paralleluniverse</groupId>
<artifactId>quasar-core</artifactId>
<version>0.7.9</version>
<classifier>jdk8</classifier>
</dependency>
3.1 开启1000000个线程
public class Main {
public static void main(String[] args) {
long start = System.nanoTime();
final CountDownLatch latch = new CountDownLatch(1000000);
//使用阻塞队列来获取结果。
for (int i = 0; i < 10000; i++) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
//开始执行
thread.start();
}
long time = System.nanoTime() - start;
System.out.printf("Tasks took %.3f ms to run%n", time/1e6);
}
}
花费时间:650.046 ms
3.2 开启1000000个协程
需要注意的是,引入的同步计数器需是Quasar中的。(3.1使用的同步计数器是jdk中的)
public class Main {
public static void main(String[] args) throws InterruptedException, ExecutionException {
long start = System.nanoTime();
final CountDownLatch latch = new CountDownLatch(1000000);
//使用阻塞队列来获取结果。
for (int i = 0; i < 10000; i++) {
//这里的Fiber就是协程
Fiber<Integer> fiber = new Fiber<>( () -> {
latch.await();
});
//开始执行
fiber.start();
}
long time = System.nanoTime() - start;
System.out.printf("Tasks took %.3f ms to run%n", time/1e6);
}
}
花费时间:214.376 ms
很明显,使用协程性能上提升了约2倍。