线程在阻塞状态和可运行状态的切换,以及线程间的上下文切换都会造成性能的损耗。为了解决这些问题,引入协程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
运行后时间只有使用线程池时的一半多一点,确实能大大缩短程序的效率。
文章参考:https://mp.weixin.qq.com/s/U1IlB_fv2BMAs5r74kDdNg