Kotlin 在1.1版本以後引入了協程的概念,目前它仍是一個試驗的API。編程
在操做系統中,咱們知道進程和線程的概念以及區別。而協程相比於線程更加輕量級,協程又稱微線程。bash
協程是一種用戶態的輕量級線程,協程的調度徹底由用戶控制。協程擁有本身的寄存器上下文和棧。協程調度切換時,將寄存器上下文和棧保存到其餘地方,在切回來的時候,恢復先前保存的寄存器上下文和棧,直接操做棧則基本沒有內核切換的開銷,能夠不加鎖的訪問全局變量,因此上下文的切換很是快。異步
Kotlin 的協程是無阻塞的異步編程方式。Kotlin 容許咱們使用協程來代替複雜的線程阻塞操做,而且複用本來的線程資源。async
Kotlin 的協程是依靠編譯器實現的, 並不須要操做系統和硬件的支持。編譯器爲了讓開發者編寫代碼更簡單方便, 提供了一些關鍵字(例如suspend
), 並在內部自動生成了一些支持型的代碼。異步編程
先舉兩個例子來講明協程的輕量級,分別建立10w個協程和10w個線程進行測試。函數
import kotlinx.coroutines.experimental.CommonPool
import kotlinx.coroutines.experimental.delay
import kotlinx.coroutines.experimental.launch
import kotlinx.coroutines.experimental.runBlocking
/** * Created by tony on 2018/7/18. */
fun main(args: Array<String>) {
val start = System.currentTimeMillis()
runBlocking {
val jobs = List(100000) {
// 建立新的coroutine
launch(CommonPool) {
// 掛起當前上下文而非阻塞1000ms
delay(1000)
println("thread name="+Thread.currentThread().name)
}
}
jobs.forEach {
it.join()
}
}
val spend = (System.currentTimeMillis()-start)/1000
println("Coroutines: spend= $spend s")
}
複製代碼
10w個協程的建立在本機大約花費 1 秒,通過測試100w個協程的建立大約花費11 秒。測試
import kotlin.concurrent.thread
/** * Created by tony on 2018/7/18. */
fun main(args: Array<String>) {
val start = System.currentTimeMillis()
val threads = List(100000) {
// 建立新的線程
thread {
Thread.sleep(1000)
println(Thread.currentThread().name)
}
}
threads.forEach { it.join() }
val spend = (System.currentTimeMillis()-start)/1000
println("Threads: spend= $spend s")
}
複製代碼
10w個線程的建立出現了OutOfMemoryError。ui
協程上下文,它包含了一個默認的協程調度器。全部協程都必須在 CoroutineContext 中執行。spa
協程調度器,它用來調度和處理任務,決定了相關協程應該在哪一個或哪些線程中執行。Kotlin 的協程包含了多種協程調度器。操作系統
按照字面意思是繼續、持續的意思。協程的執行多是分段執行的:先執行一段,掛起,再執行一段,再掛起......
Continuation 則表示每一段執行的代碼,Continuation 是一個接口。
任務執行的過程被封裝成 Job,交給協程調度器處理。Job 是一種具備簡單生命週期的可取消任務。Job 擁有三種狀態:isActive、isCompleted、isCancelled。
wait children
+-----+ start +--------+ complete +-------------+ finish +-----------+
| New | ---------------> | Active | -----------> | Completing | -------> | Completed |
+-----+ +--------+ +-------------+ +-----------+
| | |
| cancel | cancel | cancel
V V |
+-----------+ finish +------------+ |
| Cancelled | <--------- | Cancelling | <----------------+
|(completed)| +------------+
+-----------+
複製代碼
Deferred 是 Job 的子類。Job 完成時是沒有返回值的,Deferred 能夠爲任務完成時提供返回值,而且Deferred 新增了一個狀態 isCompletedExceptionally。
wait children
+-----+ start +--------+ complete +-------------+ finish +-----------+
| New | ---------------> | Active | ----------> | Completing | ---+-> | Resolved |
+-----+ +--------+ +-------------+ | |(completed)|
| | | | +-----------+
| cancel | cancel | cancel |
V V | | +-----------+
+-----------+ finish +------------+ | +-> | Failed |
| Cancelled | <--------- | Cancelling | <---------------+ |(completed)|
|(completed)| +------------+ +-----------+
+-----------+
複製代碼
協程計算能夠被掛起而無需阻塞線程。咱們使用 suspend 關鍵字來修飾能夠被掛起的函數。被標記爲 suspend 的函數只能運行在協程或者其餘 suspend 函數中。
suspend 能夠修飾普通函數、擴展函數和 lambda 表達式。
Kotlin 的協程支持多種異步模型:
這些異步機制在 Kotlin 的協程中都有實現。
Kotlin 官方對協程提供的三種級別的能力支持, 分別是: 最底層的語言層, 中間層標準庫(kotlin-stdlib), 以及最上層應用層(kotlinx.coroutines)。
使用 launch 和 async 都能啓動一個新的協程。
val job = launch {
delay(1000)
println("Hello World!")
}
Thread.sleep(2000)
複製代碼
或者
val deferred = async {
delay(1000)
println("Hello World!")
}
Thread.sleep(2000)
複製代碼
它們分別會返回一個 Job 對象和一個 Deferred 對象。
下面使用 runBlocking 來建立協程。
fun main(args: Array<String>) = runBlocking<Unit> {
launch {
delay(1000)
println("Hello World!")
}
delay(2000)
}
複製代碼
runBlocking 建立的協程直接運行在當前線程上,同時阻塞當前線程直到結束。
launch 和 async 在建立時可使用不一樣的CoroutineDispatcher,例如:CommonPool。
在 runBlocking 內還能夠建立其餘協程,例如launch。反之則不行。
Kotlin 的協程可以簡化異步編程的代碼,使用同步的方式實現異步。協程的概念和理論比較多,第一篇只是一個開始,只整理了其中一些基本概念。
Java與Android技術棧:每週更新推送原創技術文章,歡迎掃描下方的公衆號二維碼並關注,期待與您的共同成長和進步。