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为了开发者编码方便而做了一层封装。