Kotlin学习系列之:协程的创建(一)

1.协程:Coroutine

2.如何去理解协程: 可以视为轻量级的线程

  • 我们可以回顾一下什么是线程。从操作系统原理的角度来讲,进程是资源分配的基本单位,而线程是调度的基本单位,也就是说线程实际上是系统级别的,它运行与否是由操作系统决定的。从Java语言层面上讲,我们可以通过new Thread().start这种形式去启动一个线程,我们可以查看Thread类的源代码:

     public synchronized void start() {
        /**
         * This method is not invoked for the main method thread or "system"
         * group threads created/set up by the VM. Any new functionality added
         * to this method in the future may have to also be added to the VM.
         *
         * A zero status value corresponds to state "NEW".
         */
        if (threadStatus != 0)
            throw new IllegalThreadStateException();
    
        /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */
        group.add(this);
    
        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }
    
    private native void start0();
    

我们可以看到start0是一个native方法,也就是说开启一个线程实际上是需要jvm同底层操作系统进行打交道的。那么我们可以用一句话总结就是:线程是系统级别的,它的开销是巨大的,比较耗费计算机资源,而协程是轻量级的,开销小。

  • 既然协程如此轻便,那么是不是意味着我们可以抛弃线程了呢?当然不是,协程是依赖于线程的,不过协程挂起时是不会阻塞线程的,而一个线程中可以创建任意个协程

  • 协程的作用:为了简化异步编程。回想Java中,我们需要线程同步或者接口回调的方式来实现异步编程,实际上很繁琐。而像Flutter中,或者说Dart语言本身,提供了async、await等关键字来实现异步编程。Kotlin中就提供了协程来简化异步编程

3.编写第一个协程代码:

fun main() {

    GlobalScope.launch {
        delay(1000)
        println("world")
    }

    println("hello")

    Thread.sleep(2000)

    println("welcome")
}

输出结果:

hello
(等待1s)
world
(等待1s)
welcome

这段代码我们待会儿再来解释,我们再来看一个熟悉的例子:

fun main() {

    Thread{
        Thread.sleep(1000)
        println("world")
    }.start()

    println("hello")
    Thread.sleep(2000)
    println("welcome")

}

输出结果同上面一样,等待时间也是一样的。后面这段代码易懂,我们按照时间顺序来解释:

  • 0s: 启动一个子线程;子线程开始休眠;主线程打印hello;主线程开始休眠。
  • 1s:子线程到达休眠时间,唤醒后打印world
  • 2s:主线程被唤醒,打印welcome

理解到这,我们再来对比第一段协程代码:

  • 如何创建一个协程:GlobalScope.launch{...} 是不是和创建一个子线程的形式类似
  • launch后面的代码块是异步执行的,并且不会阻塞线程

同样,我们按照时间顺序来解释协程代码:

  • 0s:创建一个协程;执行delay延迟;主线程打印hello;主线程开始休眠
  • 1s:协程的delay时间已到,打印world
  • 2s:主线程被唤醒,打印welcome

现在,我们针对第一段协程代码,稍微改动一下主线程的休眠时间,将2000毫秒改为500毫秒,再来看一看运行结果:

hello
休眠0.5s
welcome

好了,到这程序就运行结束了,"world"永远也不会有机会输出。为什么呢?

  •  0s:创建一个协程;执行delay延迟;主线程打印hello;主线程开始休眠
  • 0.5s:主线程被唤醒,打印welcome;主线程运行结束,程序退出。

程序都已经退出了,那么协程当然也就不存在了。感兴趣你还可以针对第二段线程代码进行同样的修改,看看结果有什么不同,这里就不再详述了。

4.GlobalScope.launch{}

Launches a new coroutine without blocking the current thread and returns a reference to the coroutine as a [Job]

这是launch方法的描述,翻译过来就是:它会在不阻塞当前线程的前提下去启动一个新的协程,然后返回一个关联该协程的Job类型的引用。

public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job {
    val newContext = newCoroutineContext(context)
    val coroutine = if (start.isLazy)
        LazyStandaloneCoroutine(newContext, block) else
        StandaloneCoroutine(newContext, active = true)
    coroutine.start(start, coroutine, block)
    return coroutine
}

另外从源代码的角度上可以看出:launch是CoroutineScope的一个扩展方法,并且返回值是一个Job类型

  • CoroutineScope:

    /**
     * Defines a scope for new coroutines. Every coroutine builder
     * is an extension on [CoroutineScope] and inherits its [coroutineContext][CoroutineScope.coroutineContext]
     * to automatically propagate both context elements and cancellation.
    */
    public interface CoroutineScope {
    /**
         * The context of this scope.
         * Context is encapsulated by the scope and used for implementation of coroutine builders that are extensions on the scope.
         * Accessing this property in general code is not recommended for any purposes except accessing [Job] instance for advanced usages.
         *
         * By convention, should contain an instance of a [job][Job] to enforce structured concurrency.
         */
        public val coroutineContext: CoroutineContext
    }
    

    用来定义协程的作用范围,有个概念就行。

  • GlobalScope:

    public object GlobalScope : CoroutineScope {
        /**
         * Returns [EmptyCoroutineContext].
         */
        override val coroutineContext: CoroutineContext
            get() = EmptyCoroutineContext
    }
    

可以看出,GlobalScope是一个实现了CoroutineScope接口的对象,并且重写coroutineContext的get方法。

当然,CoroutineScope.launch{}只是启动协程的方式之一,Kotlin还提供了其他启动协程的方式,这些方式或者叫方法有一个名字,叫做协程建造器(coroutine builder)。我们将在接下来的篇章去进行介绍

5.补充:

我们刚刚在创建一个线程时,使用了如下代码:

Thread{
    ....
}.start()

实际上Kotlin中提供一种更为简便的方式:

thread {
    ...
}

这个thread实际上一个顶级函数(top-level function),在kotlin.concurrent包下,它的实现如下:

public fun thread(
    start: Boolean = true,
    isDaemon: Boolean = false,
    contextClassLoader: ClassLoader? = null,
    name: String? = null,
    priority: Int = -1,
    block: () -> Unit
): Thread {
    val thread = object : Thread() {
        public override fun run() {
            block()
        }
    }
    if (isDaemon)
        thread.isDaemon = true
    if (priority > 0)
        thread.priority = priority
    if (name != null)
        thread.name = name
    if (contextClassLoader != null)
        thread.contextClassLoader = contextClassLoader
    if (start)
        thread.start()
    return thread
}

实现也是一看就懂,就是Kotlin为了开发者编码方便而做了一层封装。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值