JAVA协程 纤程 与Quasar 框架

ava使用的是系统级线程,也就是说,每次调用new Thread(....).run(),都会在系统层面创建一个新的线程,然鹅新建线程的开销是很大的(每一个线程默认状况下会占用1MB的内存空间,固然你愿意的话能够用-Xss来调小点),更不要说线程切换带来的开销了git

为了节省开销,程序员玩出了不少花样。程序员

最经常使用的是线程池(线程复用,可是彻底没法处理阻塞调用的问题)github

以及事件驱动框架(NIO或者Netty,用少许的工做线程来服务大量的慢速IO链接,可是EventLoop中也不能有阻塞调用,耗时的逻辑必须放在额外的线程池里处理)编程

可是NIO的代码难写也难懂,像我这种懒惰的程序猴子,最喜欢的仍是一个线程对应一个链接这种简单粗暴的编程手法。并发

 

纤程(Coroutine)是咱们的救星框架

所谓的纤程,或者协程,能够理解为是一种轻量级的线程,它与线程的主要区别在于高并发

a. 线程切换的过程是由系统内核完成,切换的过程当中会进入到内核态。而纤程则彻底工做在用户态。oop

b. 线程是否发生切换是由操做系统决定的(抢占式调度),工做线程自己没有决定权。而纤程的切换是须要工做纤程主动放弃CPU,这样调度器才能让另一个纤程继续运行。spa

 

不少语言已经内置了纤程,最著名的应该就是Go了,用go关键字,就能直接建立一个纤程并在其中随心所欲,其余的Scheduler会自动帮你搞定。因此Go能相对容易的写出正确的高并发程序。操作系统

惋惜的是,Java没有官方的纤程支持,好在有个叫作Quasar的库可堪一用

使用这个lib,你就能在Java程序中建立纤程了,代码大概长这个样子:

复制代码
    public static void main(String[] args)
            throws ExecutionException, InterruptedException, SuspendExecution {
        int FiberNumber = 1_000_000;
        CountDownLatch latch = new CountDownLatch(1);
        AtomicInteger counter = new AtomicInteger(0);

        for (int i = 0; i < FiberNumber; i++) {
            new Fiber(() -> {
                counter.incrementAndGet();
                if (counter.get() == FiberNumber) {
                    System.out.println("done");
                }
                Strand.sleep(1000000);
            }).start();
        }
        latch.await();
    }
复制代码

在上面这段代码中,咱们直接建立了一百万个纤程,若是是通常的Thread,不考虑OS可否负担得起,单单占用的内存就要1T起步。

可是这段程序实际占用的内存只在1G出头,也就是说每一个纤程的内存占用只在1K左右。

 

这是如何作到的?

Quasar在编译时会对代码进行扫描,若是方法带有Suspendable注解,或者抛出SuspendExecution,或者在配置文件中被指定,Quasar会直接修改生成的字节码,在park方法的先后,插入一些字节码。

这些字节码会记录此时纤程的执行状态(相关的局部变量与操做数栈),而后经过抛出异常的方式将CPU的控制权从当前协程交回到控制器

此时控制器能够再次调度另一个纤程运行,并经过以前插入的那些字节码恢复当前纤程的执行状态,使程序能继续正常执行。

而且,这些操做是很是轻量的,因此内存消耗极小,也不会对CPU带来太多的额外开销(听说在3%-5%)

相关文章
相关标签/搜索