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爲了開發者編碼方便而做了一層封裝。