kotlin入門潛修之協(xié)程—基本概念

本文收錄于 kotlin入門潛修專題系列,歡迎學(xué)習(xí)交流拭宁。

創(chuàng)作不易,如有轉(zhuǎn)載瓣俯,還請(qǐng)備注杰标。

協(xié)程

從本篇文章開(kāi)始,將會(huì)開(kāi)啟kotlin的另一個(gè)世界——協(xié)程彩匕,這是kotlin提供的異步處理機(jī)制腔剂。

提到異步,自然而然想起的就是多線程驼仪、多進(jìn)程掸犬,這是繞不開(kāi)的話題,但是本系列文章將暫時(shí)不闡述他們的區(qū)別绪爸,而是先將協(xié)程相關(guān)的知識(shí)闡述完畢后湾碎,在來(lái)做一個(gè)整體的對(duì)比以及剖析協(xié)程背后的原理。

本篇文章將會(huì)演示協(xié)程的基本使用方法奠货。

Hello World介褥!

本小節(jié)先來(lái)看一個(gè)協(xié)程的“hello world”。

首先递惋,kotlin協(xié)程相關(guān)的接口是位于 kotlinx.coroutines包下的柔滔,因此,我們?cè)谑褂脜f(xié)程的時(shí)候萍虽,要首先導(dǎo)入該包睛廊。但是該包并不位于kotlin標(biāo)準(zhǔn)庫(kù)中(kotlin竟然沒(méi)有提供標(biāo)準(zhǔn)庫(kù)的協(xié)程支持),而是作為獨(dú)立的庫(kù)發(fā)布的杉编,因此我們需要先引入?yún)f(xié)程相關(guān)的依賴超全。

在這里我們采用的是maven引入的方式,如下所示:

<dependency>
    <groupId>org.jetbrains.kotlinx</groupId>
    <artifactId>kotlinx-coroutines-core</artifactId>
    <version>1.1.1</version>
</dependency>

需要注意王财,kotlin的版本一定要在版本1.3以上才可以使用卵迂,因?yàn)?.2版本中的協(xié)程還是實(shí)驗(yàn)性質(zhì)的,所以推薦使用最新的kotlin版本(對(duì)于idea可以升級(jí)kotlin插件绒净,如果無(wú)法升級(jí)則需要升級(jí)idea至最新版本)见咒,這樣才能用到相對(duì)穩(wěn)定的協(xié)程相關(guān)的接口方法。

如果是使用gradle作為構(gòu)建工具挂疆,則可以像下面一樣引入依賴:

dependencies {
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.1.1'
}

這兩種基本已經(jīng)滿足我們的使用需求了(maven常用于java工程改览,gradle則常用于android工程)下翎,其他構(gòu)建方式就不再闡述。

下面來(lái)看下kotlin協(xié)程之“hello world”示例宝当,如下所示:

fun main(args: Array<String>) {
    GlobalScope.launch {
        delay(1000L)
        println("world!")
    }

    println("hello ")
    Thread.sleep(2000L)
}

上面的代碼執(zhí)行完成后视事,打印結(jié)果如下所示:

hello 
world!

GlobalScope.launch表示啟動(dòng)一個(gè)后臺(tái)協(xié)程,該協(xié)程的任務(wù)是延時(shí)1000ms后打印“world!”字符庆揩。當(dāng)kotlin啟動(dòng)過(guò)該協(xié)程后俐东,會(huì)繼續(xù)執(zhí)行其后面的代碼,所以我們看到先打印出了“hello ”字符串订晌。需要注意虏辫,我們?cè)诖蛴ello字符串語(yǔ)句之后又調(diào)用了Thread.sleep方法,并延時(shí)等待了2000ms(理論上只要稍微大于GlobalScope.launch中的延時(shí)時(shí)間即可)锈拨,這么做的原因是保持jvm繼續(xù)存活砌庄,否則程序執(zhí)行完后(即main方法線程執(zhí)行完后)就會(huì)被終止,而不會(huì)執(zhí)行GlobalScope.launch中延時(shí)后的打印語(yǔ)句(后面會(huì)有新的機(jī)制可以不必這么做)奕枢,這點(diǎn)和多線程不一樣娄昆。此外,采用GlobalScope啟動(dòng)的協(xié)程(它是top-level級(jí)別的協(xié)程)缝彬,其生命周期將會(huì)伴隨整個(gè)應(yīng)用程序的生命周期萌焰。

再來(lái)看一下delay方法,該方法的聲明如下所示:

suspend fun delay(timeMillis: Long):Unit

該方法的意思是跌造,延遲協(xié)程的執(zhí)行杆怕,延遲時(shí)間由timeMillis決定,表示延遲的毫秒數(shù)壳贪。delay方法并不會(huì)阻塞線程陵珍,而僅僅會(huì)掛起當(dāng)前協(xié)程,并在其指定延遲的時(shí)間到達(dá)時(shí)喚醒當(dāng)前協(xié)程违施。

由上面代碼可知互纯,delay方法使用了suspend關(guān)鍵字進(jìn)行修飾,故名思議磕蒲,suspend修飾的方法表示該方法是個(gè)可掛起方法留潦,只能用于協(xié)程中或者被suspend方法調(diào)用。

上面示例中辣往,實(shí)際上用到了java中的Thread線程機(jī)制(用于阻塞main線程)兔院,其實(shí)kotlin本身也提供了一種機(jī)制來(lái)阻塞線程,如下所示:

fun main(args: Array<String>) {
    GlobalScope.launch {
        delay(1000L)
        println("world!")
    }
    println("hello ")
    runBlocking {
        delay(2000L)
    }
}

上面代碼執(zhí)行結(jié)果同樣會(huì)打印“hello world!”站削,runBlocking的作用就是用于阻塞main線程坊萝,以保持jvm的存活(否則程序運(yùn)行結(jié)束后會(huì)被回收)。注意,前面說(shuō)過(guò)delay方法并不阻塞線程十偶,所以這里不要誤以為是delay方法在阻塞main線程菩鲜,其背后的機(jī)制實(shí)際上是,main線程會(huì)等待runBlocking中的代碼執(zhí)行完成惦积!

我們也可以結(jié)合runBlocking來(lái)采用多協(xié)程的方式完成上述功能接校,如下所示:

fun main(args: Array<String>) = runBlocking {
    GlobalScope.launch {
        delay(1000L)
        println("world!")
    }
    println("hello ")
    delay(2000L)
}

打印結(jié)果同上。這里需要注意狮崩,我們使用runBlocking方法啟動(dòng)了main協(xié)程蛛勉,然后又在main協(xié)程中啟動(dòng)了一個(gè)新的后臺(tái)協(xié)程(即GlobalScope.launch ),因?yàn)閙ain協(xié)程中的delay方法運(yùn)行在runBlocking方法中厉亏,所以這種寫(xiě)法董习,依然能起到“阻塞main線程”的目的(main線程需要等待runBlocking運(yùn)行完成)。

runBlocking<Unit>中的Unit表示main方法的返回值爱只,這里只不過(guò)是進(jìn)行了顯示指定,其默認(rèn)返回值其實(shí)就是Unit招刹。

使用delay方法可以起到延遲等待的作用恬试,但是這顯然不是一個(gè)好的實(shí)現(xiàn)方式,畢竟程序執(zhí)行的時(shí)間有些時(shí)候并不是確定的疯暑。如果我們想實(shí)現(xiàn)健全的等待機(jī)制训柴,可以利用kotlin為我們提供的job,如下所示:

fun main(args: Array<String>) = runBlocking {
    val job = GlobalScope.launch {
        delay(1000L)
        println("world!")
    }
    println("hello ")
    job.join()//這里會(huì)等待協(xié)程job執(zhí)行完成
}

上面job的類型是kotlin為我們提供的接口Job妇拯,表示一個(gè)任務(wù)幻馁。后面文章會(huì)進(jìn)行闡述。

結(jié)構(gòu)化并發(fā)

在kotlin中越锈,我們使用GlobalScope.launch啟動(dòng)的協(xié)程是top-level級(jí)別仗嗦,雖然協(xié)程是輕量級(jí)的,但是依然會(huì)消耗資源甘凭。比如我們啟動(dòng)了很多協(xié)程稀拐,在運(yùn)行的時(shí)候發(fā)生了未知錯(cuò)誤,如果沒(méi)有一個(gè)健全的機(jī)制回收這些資源丹弱,那么kotlin是不會(huì)自動(dòng)幫我們處理的德撬,針對(duì)這種情況,我們可以使用結(jié)構(gòu)化并發(fā)躲胳。即像使用線程一樣蜓洪,在需要的時(shí)候創(chuàng)建,而不是創(chuàng)建top-level級(jí)別的協(xié)程坯苹,如下所示:

fun main(args: Array<String>) = runBlocking {
    launch {
        delay(500L)
        println("world!")
    }
    println("hello ")
}

上面代碼同樣會(huì)打印'hello world'隆檀,與前面不同的是,我們沒(méi)有再讓main協(xié)程等待一定的時(shí)間,也沒(méi)有使用join機(jī)制刚操。這是因?yàn)檎⒊幔琹aunch創(chuàng)建的協(xié)程,其作用域?qū)儆趓unBlocking(參見(jiàn)下節(jié)作用域構(gòu)造器)菊霜,此時(shí)坚冀,runBlocking會(huì)等待在作用域內(nèi)啟動(dòng)的所有協(xié)程的執(zhí)行,直到他們?nèi)客瓿刹艜?huì)結(jié)束鉴逞。

對(duì)于上面的代碼记某,我們還可以結(jié)合前面提到的suspend方法做進(jìn)一步整理,那就是將launch中的語(yǔ)句抽象出來(lái)构捡,作為一個(gè)單獨(dú)的方法液南,如下所示:

fun main(args: Array<String>) = runBlocking {
    launch {
        printWorld()
    }
    println("hello ")
}
//這里必須定義為suspend方法,否則無(wú)法使用同時(shí)suspend的delay方法勾徽。
suspend fun printWorld() {
    delay(500L)
    println("world!")
}

作用域構(gòu)造器(Scope builder)

上面提到的協(xié)程作用域滑凉,實(shí)際上就是由不同的作用域構(gòu)造器決定的,除了使用系統(tǒng)提供的默認(rèn)作用域構(gòu)造器喘帚,我們同樣可以自己定義作用域構(gòu)造器畅姊。kotlin為我們提供了創(chuàng)建作用域構(gòu)造器的方法:coroutineScope。使用該方法會(huì)創(chuàng)建一個(gè)新的協(xié)程作用域吹由,coroutineScope和runBlocking很相似若未,他們都會(huì)等待其作用域內(nèi)的所有協(xié)程執(zhí)行完成后才會(huì)結(jié)束,但是coroutineScope不會(huì)像runBlocking那樣阻塞線程倾鲫。來(lái)看個(gè)例子:

fun main(args: Array<String>) = runBlocking {
    launch {//launch屬于runBlocking作用域
        delay(200L)
        println("Task from runBlocking")
    }
   //創(chuàng)建了一個(gè)新的協(xié)程作用域粗合,屬于runBlocking作用域
    coroutineScope {
        launch {//屬于coroutineScope作用域
            delay(300L)
            println("Task from nested launch in  coroutine scope")
        }

        delay(100L)
        println("Task from coroutine scope")
    }
   //屬于runBlocking作用域,該打印語(yǔ)句永遠(yuǎn)會(huì)在coroutineScope
//返回之后執(zhí)行乌昔,但是如果和開(kāi)始處的launch方法中的打印時(shí)機(jī)進(jìn)行對(duì)比
//則取決于launch的delay時(shí)間
    println("coroutine scope is over")
}

我們先分析下上面的代碼:

首先隙疚,我們使用runBlocking創(chuàng)建了一個(gè)main協(xié)程,在其代碼塊中玫荣,我們使用launch啟動(dòng)了一個(gè)新協(xié)程甚淡,該協(xié)程作用域?qū)儆趍ain協(xié)程,launch啟動(dòng)的協(xié)程執(zhí)行時(shí)機(jī)將會(huì)有其內(nèi)部的delay時(shí)間決定捅厂。

接著贯卦,我們使用coroutineScope創(chuàng)建了一個(gè)新的協(xié)程作用域,該協(xié)程會(huì)等待其作用域內(nèi)的所有協(xié)程執(zhí)行完成后才會(huì)結(jié)束焙贷。這意味著撵割,在這個(gè)示例中,只有coroutineScope內(nèi)部的所有協(xié)程執(zhí)行完成之后辙芍,coroutineScope才會(huì)返回啡彬,也就是說(shuō) println("coroutine scope is over")
這條語(yǔ)句永遠(yuǎn)都會(huì)在coroutineScope返回之后才執(zhí)行羹与。

最后,再結(jié)合不同的delay時(shí)間就不難推測(cè)出打印結(jié)果庶灿,如下所示:

Task from coroutine scope
Task from runBlocking
Task from nested launch in  coroutine scope
coroutine scope is over

Global 協(xié)程

前面已經(jīng)多次提到了Global協(xié)程纵搁,這里在明確一下它的概念。

在kotlin中往踢,Global協(xié)程相當(dāng)于后臺(tái)線程的概念腾誉,即Gloabal協(xié)程并不會(huì)保證進(jìn)程的存活,應(yīng)用程序結(jié)束了它就結(jié)束了峻呕,來(lái)看個(gè)例子:

fun main(args: Array<String>) = runBlocking {
    GlobalScope.launch {
        repeat(100) { i ->
            println(i)
            delay(100L)
        }
    }

    delay(300L)
    println("end...")
}

上面代碼執(zhí)行過(guò)后打印如下:

0
1
2
3
end...

也就是說(shuō)利职,程序什么時(shí)候結(jié)束,取決于runBlocking方法delay的時(shí)間瘦癌,這也就是文章剛開(kāi)始的時(shí)候猪贪,我們使用Global協(xié)程,為什么要等待一定時(shí)間的原因讯私。當(dāng)然热押,看到這里,我們顯然有很多解決方法了(比如將GlobalScope.launch改為launch)斤寇,這里只是更進(jìn)一步的說(shuō)明一下GlobalScope協(xié)程的機(jī)制楞黄。

至此,本篇文章闡述完畢抡驼。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市肿仑,隨后出現(xiàn)的幾起案子致盟,更是在濱河造成了極大的恐慌,老刑警劉巖尤慰,帶你破解...
    沈念sama閱讀 222,681評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件馏锡,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡伟端,警方通過(guò)查閱死者的電腦和手機(jī)杯道,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,205評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)责蝠,“玉大人党巾,你說(shuō)我怎么就攤上這事∷剑” “怎么了齿拂?”我有些...
    開(kāi)封第一講書(shū)人閱讀 169,421評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)肴敛。 經(jīng)常有香客問(wèn)我署海,道長(zhǎng)吗购,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 60,114評(píng)論 1 300
  • 正文 為了忘掉前任砸狞,我火速辦了婚禮捻勉,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘刀森。我一直安慰自己踱启,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,116評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布撒强。 她就那樣靜靜地躺著禽捆,像睡著了一般。 火紅的嫁衣襯著肌膚如雪飘哨。 梳的紋絲不亂的頭發(fā)上胚想,一...
    開(kāi)封第一講書(shū)人閱讀 52,713評(píng)論 1 312
  • 那天,我揣著相機(jī)與錄音芽隆,去河邊找鬼浊服。 笑死,一個(gè)胖子當(dāng)著我的面吹牛胚吁,可吹牛的內(nèi)容都是我干的牙躺。 我是一名探鬼主播,決...
    沈念sama閱讀 41,170評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼腕扶,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼孽拷!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起半抱,我...
    開(kāi)封第一講書(shū)人閱讀 40,116評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤脓恕,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后窿侈,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體炼幔,經(jīng)...
    沈念sama閱讀 46,651評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,714評(píng)論 3 342
  • 正文 我和宋清朗相戀三年史简,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了乃秀。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,865評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡圆兵,死狀恐怖跺讯,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情衙傀,我是刑警寧澤抬吟,帶...
    沈念sama閱讀 36,527評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站统抬,受9級(jí)特大地震影響火本,放射性物質(zhì)發(fā)生泄漏危队。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,211評(píng)論 3 336
  • 文/蒙蒙 一钙畔、第九天 我趴在偏房一處隱蔽的房頂上張望茫陆。 院中可真熱鬧,春花似錦擎析、人聲如沸簿盅。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,699評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)桨醋。三九已至,卻和暖如春现斋,著一層夾襖步出監(jiān)牢的瞬間喜最,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,814評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工庄蹋, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留瞬内,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,299評(píng)論 3 379
  • 正文 我出身青樓限书,卻偏偏與公主長(zhǎng)得像虫蝶,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子倦西,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,870評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容