kotlin之協(xié)程的理解與使用詳解
前言
為什么在kotlin要使用協(xié)程呢,這好比去了重慶不吃火鍋一樣的道理。協(xié)程的概念并不陌生,在python也有提及。任何事務(wù)的作用大多是對(duì)于所依賴的環(huán)境相應(yīng)而生的,協(xié)程對(duì)于kotlin這門語(yǔ)言也不例外。協(xié)程的優(yōu)點(diǎn),總的來(lái)說(shuō)有如下幾點(diǎn):輕量級(jí),占用更少的系統(tǒng)資源; 更高的執(zhí)行效率; 掛起函數(shù)較于實(shí)現(xiàn)Runnable或Callable接口更加方便可控; kotlin.coroutine 核心庫(kù)的支持,讓編寫異步代碼更加簡(jiǎn)單。當(dāng)然在一些不適應(yīng)它的用法下以上優(yōu)勢(shì)也會(huì)成為劣勢(shì)。
1.協(xié)程定義
協(xié)程定義:kotlin官方基于JVM的線程實(shí)現(xiàn)的一個(gè)并發(fā)任務(wù)處理框架,封裝的線程api
- 使用方便,不使用回調(diào)實(shí)現(xiàn)線程切換,使用同步方式寫出異步代碼
- 所有的耗時(shí)任務(wù)保證一定放在后臺(tái)執(zhí)行掛起
- 函數(shù)執(zhí)行完畢之后,協(xié)程會(huì)把它切換到原先的線程的線程。
2.協(xié)程的基本用法
常規(guī)函數(shù)中一般都有:call and return,協(xié)程在此之外添加了suspend和resume.
- suspend 用于暫停執(zhí)行的當(dāng)前協(xié)程,并保存所有的局部變量
- resume 用于已暫停的協(xié)程中暫停出恢復(fù)
supend(掛起函數(shù))是什么,有什么意義
suspend,對(duì)協(xié)程的掛起并沒(méi)有實(shí)際作用,其實(shí)只是一個(gè)提醒,函數(shù)創(chuàng)建者對(duì)函數(shù)的調(diào)用者的提醒,提醒調(diào)用者我是需要耗時(shí)操作,需要用掛起的方式,在協(xié)程中使用.
- 需要注意的是掛起函數(shù)只能在掛起函數(shù)或者協(xié)程作用域中使用,為什么掛起函數(shù)需要在協(xié)程作用域中使用?因?yàn)槠胀ê瘮?shù)沒(méi)有suspend和resume這兩個(gè)特性,所以必須要在協(xié)程的作用中使用。
- 意義:
- 語(yǔ)法層面:作為一個(gè)標(biāo)記和提醒。通過(guò)報(bào)錯(cuò)來(lái)提醒調(diào)用者和編譯器,這是一個(gè)耗時(shí)函數(shù),需要放在后臺(tái)執(zhí)行。
- 編譯器層面:輔助 Kotlin 編譯器來(lái)把代碼轉(zhuǎn)換成 JVM 的字節(jié)碼。
- 怎么自定義suspend函數(shù)?
- 什么時(shí)候定義?
- 需要耗時(shí)操作的時(shí)候,需要定義,例如io耗時(shí)操作(請(qǐng)求網(wǎng)絡(luò));獲取數(shù)據(jù)庫(kù)數(shù)據(jù);一些等待一會(huì)需要的操作;列表排除,json解析等;
- 怎么寫suspend函數(shù),給函數(shù)前加上suspend 關(guān)鍵字,把內(nèi)容用withContext包起來(lái)
suspend fun testSuspendfun(){
withContext(Dispatchers.IO){
}
}
協(xié)程如何確保主線程安全
- Dispatchers.Main 調(diào)用程序在Android的主線程中
- Dispatchers.IO 適合主線程之外的執(zhí)行磁盤或者網(wǎng)絡(luò)io操作,例如文件的讀取與寫入,任何的網(wǎng)絡(luò)請(qǐng)求
- Dispatcher.Default 適合主線程之外的,cpu的操作,例如json數(shù)據(jù)的解析,以及列表的排序,
協(xié)程的掛起本質(zhì):本質(zhì)就是切線程,完成之后只不過(guò)可以自動(dòng)切回來(lái)
協(xié)程掛起就是切個(gè)線程,在掛起函數(shù)執(zhí)行完畢之后,協(xié)程會(huì)自動(dòng)的重新切回它原先的線程,也就是稍后會(huì)被切回來(lái)的線程切換。切回來(lái)就是resume,恢復(fù)功能是協(xié)程,所以suspend函數(shù)需要在另一個(gè)suspend函數(shù)或者協(xié)程中調(diào)用?!阜亲枞綊炱稹棺枞姆绞綄懗隽朔亲枞姆绞?。
3.協(xié)程的創(chuàng)建以及取消
//創(chuàng)建一個(gè)協(xié)程
Val scope = CoroutineScope(Dispatchers.Main+Job())
通過(guò)Job獲取協(xié)程的生命周期
scope.launch{
}
其他耗時(shí)請(qǐng)求,例如從數(shù)據(jù)庫(kù)中獲取數(shù)據(jù)
scope.async {
}
在KTX庫(kù)為某些生命周期提供自己的CoroutineScope,例如ViewModel中viewModelScope,Lifecycle有l(wèi)ifecycleScope
協(xié)程的啟動(dòng),launch 啟動(dòng)新協(xié)程而不將結(jié)果返回給調(diào)用方
//創(chuàng)建之后,不管后續(xù)
launch(){
}
async 啟動(dòng)一個(gè)新協(xié)程,并通過(guò)deferred的await方法暫停函數(shù)
//返回deferred 對(duì)象
val deferred async{
}
deferred.await()
協(xié)程的結(jié)構(gòu)化并發(fā),取消協(xié)程
協(xié)程的結(jié)構(gòu)化并發(fā),可以讓協(xié)程非常便于管理。例如在關(guān)閉activity中要取消協(xié)程。如果是在線程中,取消所有的線程比較復(fù)雜。
取消父協(xié)程以及父里面的子協(xié)程
val scope = CoroutineScope(Dispatchers.Main+ Job())
scope.launch {
val job = launch {
val job1 = launch {
}
}
job.cancel()
}
scope.cancel()
取消子協(xié)程某一個(gè),每一個(gè)協(xié)程都會(huì)返回一個(gè)job對(duì)象,通過(guò)調(diào)用job的cancle,可以去取消單個(gè)的協(xié)程的。
val scope = CoroutineScope(Dispatchers.Main+ Job())
scope.launch {
val job = launch {
val job1 = launch {
}
}
job.cancel()
}
scope.cancel()
4.協(xié)程中異常處理
在協(xié)程內(nèi)部中捕獲異常
val scope = CoroutineScope(Dispatchers.Main+ Job())
scope.launch {
try {
}catch (e:Exception){
}
}
5.協(xié)程的優(yōu)勢(shì)
在程序運(yùn)行過(guò)程中某些操作(像是:網(wǎng)絡(luò)IO、文件IO、CPU或GUP計(jì)算密集型工作等等)可能會(huì)耗費(fèi)大量的時(shí)間,在單線程的環(huán)境下可能會(huì)造成線程的阻塞,在他們完成之前沒(méi)辦去做其它事情。使用傳統(tǒng)方法的話,我們可能會(huì)選擇使用多線程來(lái)解決這個(gè)問(wèn)題,將這些耗時(shí)操作放置到新的線程中去執(zhí)行,使主線程能夠正常的運(yùn)行。那么本文標(biāo)題所提到的協(xié)程是怎么一回事呢?
協(xié)程可以看作是一個(gè)輕量級(jí)的線程,他不是由操作系統(tǒng)或是虛擬機(jī)來(lái)實(shí)現(xiàn)的,而是通過(guò)編譯器。這意味著相對(duì)于線程,協(xié)程的開銷更小。大家可以從下面的這個(gè)例子中感受一下。
下面是一段Kotlin使用協(xié)程的代碼,創(chuàng)建了100萬(wàn)個(gè)協(xié)程 (官方的例子是使用的100K,不過(guò)運(yùn)行時(shí)間太短,不好截內(nèi)存的使用情況)。
fun main(args: Array)= runBlocking { val jobs= List(1_000_000){
launch(CommonPool){
delay(10L)
println(it)
}
}
jobs.forEach { it.join() }
}
內(nèi)存使用情況

運(yùn)行耗時(shí):

然后是使用線程來(lái)進(jìn)行實(shí)現(xiàn)的代碼:
fun main(args: Array) { val threadList=List(1_000_000){
Thread{
Thread.sleep(10L)
println(it)
}
}
threadList.forEach { it.start();it.join() }
}
內(nèi)存使用情況:

運(yùn)行耗時(shí):10分鐘以上。
使用線程的代碼,占用的內(nèi)存幾乎是使用協(xié)程的兩倍。而且從運(yùn)行時(shí)間上看使用協(xié)程實(shí)現(xiàn)的程序話費(fèi)的時(shí)間要遠(yuǎn)遠(yuǎn)低于線程的實(shí)現(xiàn)方式。單從這兩點(diǎn)來(lái)看,協(xié)程擁有更高的執(zhí)行效率,占用更少的系統(tǒng)資源。那么Kotlin中的協(xié)程是通過(guò)什么來(lái)實(shí)現(xiàn)異步操作的呢?它使用的是一種叫做 掛起 的機(jī)制。協(xié)程的掛起幾乎是沒(méi)有損耗的,換種說(shuō)法,就是不需要選擇額外的上下文或是操作系統(tǒng)調(diào)用。 另外一點(diǎn), 掛起能很大程度上被用戶庫(kù)給控制:我們可以決定在掛起狀態(tài)下具體做些什么,并且圍繞著需求進(jìn)行優(yōu)化/日志/攔截等操作。
協(xié)程不能隨隨便便就被掛起,只能在一個(gè)稱為掛起點(diǎn)的地方,在這里會(huì)去調(diào)用特別標(biāo)記的函數(shù)。這樣的函數(shù)被稱作 掛起函數(shù),因?yàn)槟阏{(diào)用他們會(huì)掛起一個(gè)協(xié)程(如果允許這次調(diào)用的話,庫(kù)可以直接進(jìn)行處理而不需要掛起)。 掛起函數(shù)的聲明需要添加suspend修飾符。例如:
suspend fun doSomething(foo: Foo): Bar {
...
}
掛起函數(shù)就像平常使用的函數(shù)一樣,可以有參數(shù)和返回值,但是他們只能被協(xié)程或是其它掛起函數(shù)調(diào)用。事實(shí)上,要想啟動(dòng)一個(gè)協(xié)程,至少得有一個(gè)掛起函數(shù),并且一般是匿名的(也就是一個(gè)掛起lambda表達(dá)式)。
線程往往是沒(méi)有返回值(實(shí)現(xiàn)Runnable接口),盡管可以通過(guò)實(shí)現(xiàn)Callable接口來(lái)獲得帶返回值的線程。但這與協(xié)程在語(yǔ)法層面上的支持,在使用的便捷性上還是有不少差距的。
協(xié)程是通過(guò)編譯技術(shù)實(shí)現(xiàn)的 (不需要虛擬機(jī)或操作系統(tǒng)的特別支持),這一點(diǎn)在開頭也提到了。掛起操作通過(guò)代碼變換實(shí)現(xiàn)?;旧?,每一個(gè)掛起函數(shù)(可能會(huì)進(jìn)行優(yōu)化,但我們?cè)谥幌胗懻撨@點(diǎn))都被轉(zhuǎn)換成一個(gè)狀態(tài)機(jī),那些狀態(tài)與掛起調(diào)用相對(duì)應(yīng)。在一個(gè)掛起準(zhǔn)備好之前,下一狀態(tài)與相關(guān)局部變量等一起存儲(chǔ)在編譯器生成的類的字段中。在恢復(fù)該協(xié)程時(shí),恢復(fù)局部變量并且狀態(tài)機(jī)從剛好掛起之后的狀態(tài)進(jìn)行。掛起的協(xié)程可以作為保持其掛起狀態(tài)與局部變量的對(duì)象來(lái)存儲(chǔ)和傳遞。
許多其它語(yǔ)言實(shí)現(xiàn)的異步機(jī)制也能制作成庫(kù),在Kotlin的協(xié)程中使用。包括:C#和ECMAScript寫的 async/await , channels Go語(yǔ)言寫的 select ;C#和Python寫的 generators/yield 。
到此這篇關(guān)于kotlin之協(xié)程的理解與使用詳解的文章就介紹到這了,更多相關(guān)kotlin之協(xié)程內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Hibernate三種狀態(tài)和Session常用的方法
本文主要介紹了Hibernate三種狀態(tài)和Session常用的方法,具有很好的參考價(jià)值,下面跟著小編一起來(lái)看下吧2017-03-03
Java實(shí)現(xiàn)生成pdf并解決表格分割的問(wèn)題
這篇文章主要為大家詳細(xì)介紹了如何利用Java實(shí)現(xiàn)生成pdf,并解決表格分割的問(wèn)題,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-11-11
springboot整合redis進(jìn)行數(shù)據(jù)操作(推薦)
springboot整合redis比較簡(jiǎn)單,并且使用redistemplate可以讓我們更加方便的對(duì)數(shù)據(jù)進(jìn)行操作。下面通過(guò)本文給大家分享springboot整合redis進(jìn)行數(shù)據(jù)操作的相關(guān)知識(shí),感興趣的朋友一起看看吧2017-10-10
Java?中很好用的數(shù)據(jù)結(jié)構(gòu)EnumSet
這篇文章主要介紹了Java?中很好用的數(shù)據(jù)結(jié)構(gòu)EnumSet,EnumMap即屬于一個(gè)Map,下文圍繞主題展開詳細(xì)內(nèi)容,需要的小伙伴可以參考參考一下2022-05-05
SpringBoot修改子模塊Module的jdk版本的方法 附修改原因
這篇文章主要介紹了SpringBoot修改子模塊Module的jdk版本的方法 附修改原因,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-04-04
java 線程方法join簡(jiǎn)單用法實(shí)例總結(jié)
這篇文章主要介紹了java 線程方法join簡(jiǎn)單用法,結(jié)合實(shí)例形式總結(jié)分析了Java線程join方法的功能、原理及使用技巧,需要的朋友可以參考下2019-11-11
IDEA 自定義方法注解模板的實(shí)現(xiàn)方法
這篇文章主要介紹了IDEA 自定義方法注解模板的實(shí)現(xiàn)方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-09-09
springboot如何獲取application.yml里值的方法
這篇文章主要介紹了springboot如何獲取application.yml里的值,文章圍繞主題相關(guān)自資料展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-04-04

