kotlin 协程执行过程java代码化,让我们来了解其中的奥秘

1,100 阅读5分钟

kotlin 协程执行过程java代码化,让我们来了解其中的奥秘

ps:写文章不是强项,有什么问题看代码事例~~

开始

GlobalScope.launch(Dispatchers.Main) {
    Log.e("launch withContext ", " 1111111 " + Thread.currentThread())
    val intValue = async(context = Dispatchers.IO) {
        delay(3000)
        1
        Log.e("launch withContext ", " 22222222 " + Thread.currentThread())
    }
    val value = intValue.await()
    Log.e("launch withContext", "value  " + value + "    " + Thread.currentThread())
 

以上是一段最常用的标准代码样例,编译后这整段代码都会提到,kotlin自动生成的类里面,类似SuspendLambdaMain

class SuspendLambdaMain extends SuspendLambda {
    //1.每执行一个case,label都会被置为下一个case对应的int值
    int label;
    public SuspendLambdaMain(TestContext dispatch) {
        super(null, dispatch);
    }
    //2.协程为我们分装的执行类,把执行和线程切换包装在一起
    private TestContinuation continuation;

    @Override
    void onCreate(TestContinuation testContinuation) {
        this.continuation = testContinuation;
    }

    @Override
    public final Object invokeSuspend(Object result) {
        int i = this.label;
        if (i == 0) {
            // 这里把label置为了1,也就是else if里的执行条件达成
            this.label = 1;
            Log.e("launch withContext ", " 1111111 " + Thread.currentThread());
            
            //3.async的模类
            new SuspendLambdaAsync(continuation, TestDispatchers.IO).intercepted().resumeWith(null);
            //为了方便,这里直接判断挂起状态,实际会根据是否执行完毕,来放回对应的值
            return State.COROUTINE_SUSPENDED;
        } else if (i == 1) {
            int value = (int) result;
            Log.e("launch withContext", "value  " + value + "    " + Thread.currentThread());
        }

        return Unit.INSTANCE;
    }
}

1处label是一个标记位,执行invokeSuspend的第一个case(也就是 if(i==0))代码段后,label变为1,当invokeSuspend再 次执行的时候,就会执行第二个case,所以通过label也就实现了我们launch里逐行调用对应的代码

2处,TestContinuation的作用就是把context(可以理解为线程切换帮助类)和对应的代码执行类结合起来,能够实现逻辑跑在指定的线程

接下来我们看3处对应生成的类SuspendLambdaAsync

class SuspendLambdaAsync extends SuspendLambda {
    int label;
    public SuspendLambdaAsync(TestContinuation suspendLambda, TestContext context) {
        super(suspendLambda, context);
    }
    private TestContinuation continuation;
    @Override
    void onCreate(TestContinuation testContinuation) {
        this.continuation = testContinuation;
    }

    @Override
    public Object invokeSuspend(Object result) {
        switch (label) {
            case 0:
                label = 1;
                //4.delay的实现
                State state = Delay.delay(continuation, 3000);
                if (state == State.COROUTINE_SUSPENDED) {
                    return state;
                }
            case 1:
                Log.e("launch withContext ", " 22222222 " + Thread.currentThread());
                return 1;
        }
        return null;
    }
}

SuspendLambdaAsync的实现和SuspendLambdaMain没什么大的区别
4处也就是一个延时的实现,我们之后再看

我们先来看看launch原本的位置的代码模拟

SuspendLambdaMain(TestDispatchers.MAIN).intercepted().resumeWith(null)

重点方法intercepted ,看SuspendLambdaMain的父类

public abstract class TestContinuationImp extends TestContinuation<Object> {
    //5.本类的执行者,本类执行结束,会通过completion恢复执行者的执行,也就是回调到之前执行过的invokeSuspend的下一个case
    private final TestContinuation completion;
    //线程切换相关
    private final TestContext context;

    public TestContinuationImp(TestContinuation completion, TestContext context) {
        this.completion = completion;
        this.context = context;
    }

    private TestContinuation intercepted;
    public final TestContinuation intercepted() {
        if (intercepted != null) {
            return intercepted;
        }
        //6.执行和线程切换组合类
        intercepted = new TestDispatchedContinuation(this, context);
        // 这里回传执行和线程切换组合类
        onCreate(intercepted);
        return intercepted;
    }

    @Override
    public void resumeWith(Object result) {
        Object value;
        //7.判断执行状态,需要挂起直接return
        if ((value = invokeSuspend(result)) == State.COROUTINE_SUSPENDED) {
            return;
        }
        if (completion != null) {
            completion.resumeWith(value);
        }
    }

    abstract Object invokeSuspend(Object result);
    abstract void onCreate(TestContinuation testContinuation);
}

5处completion,也就是那个调用其自己的对象,当自己执行完,completion会继续执行,事例只有成功的情况,当然还有异常和取消
6处线程执行和切换类的包装
7.判断执行需不需要挂起,挂起直接return

接下来,我们看看线程切换实现类的模拟TestDispatchers
重点看一下IO切换实现

static class IO extends TestDispatcher {
    public IO() {
        plusContext(this);
    }

    private AtomicLong atomicLong = new AtomicLong();
    private Executor executor = new ThreadPoolExecutor(2, Integer.MAX_VALUE, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>()
            , new ThreadFactory() {
        @Override
        public Thread newThread(Runnable r) {
            Thread thread = new Thread(r);
            thread.setName("IO: " + atomicLong.getAndIncrement());
            return thread;
        }
    });

    @Override
    void dispatch(Runnable task) {
        executor.execute(task);
    }
    //8.delay的具体实现
    public void delay(final Runnable task, long delayMillis) {
        defaultExecutor.delayQueue.add(new RunnableTime(new Runnable() {
            @Override
            public void run() {
                dispatch(task);
            }
        }, delayMillis));
    }
}

线程切换就没什么好说的,主线程dispatch这是直接通过handle,都是常规操作
8处就是延时的实现也就是注释4处具体的实现,延时在不同的环境下,可以有不同的实现,默认都是通过协程的DefaultExecutor实现
看看Default的模拟类

static class DefaultExecutor implements Runnable {
    LinkedBlockingQueue<RunnableTime> delayQueue = new LinkedBlockingQueue<>();
    public DefaultExecutor() {
        new Thread(this, "Default").start();
    }
    @Override
    public void run() {
       //9.循环执行任务
        while (true) {
            RunnableTime runnableTime = delayQueue.peek();
            if (runnableTime != null) {
                if (runnableTime.time < System.currentTimeMillis()) {
                    runnableTime.run();
                    delayQueue.remove(runnableTime);
                }
            }
            //虽然存在阻塞,但是这始终是一个死循环
            LockSupport.parkNanos(this, processNextEvent());
        }
    }
    long processNextEvent() {
        long waitTime = 1000000000;
        RunnableTime runnableTime = delayQueue.peek();
        if (runnableTime != null) {
            waitTime = runnableTime.time - System.currentTimeMillis();
        }
        return waitTime;
    }
}
static class RunnableTime implements Runnable {
    Runnable runnable;
    long time;
    RunnableTime(Runnable runnable, long delayMillis) {
        this.runnable = runnable;
        this.time = delayMillis + System.currentTimeMillis();
    }
    @Override
    public void run() {
        runnable.run();
    }
}

延时执行就更没什么秘密了,内部维护类似handle的messageQueue的那套东西,当然还有任务添加,执行时序调整等内容我没有去写, 因为这部分本来就可以看到源码,就不做过多解释了
9处,是看到目前比较不理解的地方,这是个死循环,协程内部就是个死循环,且这个实现又不小messageQueue,在没有任务的时候, 这个循环仍然以一定时间间隔在跑,打上断点就可以看到。有谁了解,可以提点一下哇~~

coroutines vs rxjava

从实现上来讲,协程并没什么特别的优势,所谓挂起,也只是让代码能顺序执行,支撑起这些的还是线程、线程切换和回调这些东西。
从写法上讲,协程确实带了了全新的体验,但这一切都需要编译器的支持,写的代码和最终的样子差距很大,这一点可能在反破解、混淆上可能有点好处, 不过看源码实现上也会相对的显得吃力点,同时这也是一些人不喜欢用kotlin、Lambda的原因,自己写写还好,但是看别人写的,接手别人的代码, 熟悉起来肯定比不过java。
rxajva的Subject,也是比较常用的,且强大的,虽然协程也有channel,但是必须写在协程的作用域,使用体验上和java的Subject还是有不小的差距

rxjava 链式调用它真的就不香了么?各种操作符就不方便了么?
rxjava则是纯代码形式,不存在什么黑魔法,和大多数java库一样,源码阅读上还算是简单明了的。

链式写法,链的太深可能会照成crash不好找,但是 对于它所带来的优势比,这完全可以接受,不然rx相关的库为啥这么多呢。