聊聊kotlin.coroutines【java協程】(1)

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

這個夏天java最有競爭力的語言。關於它的文法糖在這就不一一闡述了,畢竟它能甜死你。
先說說什麼是協程吧,使用者態的子線程,輕量級,進程->線程->協程。

進程、線程、協程的關係和區別:
進程擁有自己獨立的堆和棧,既不共用堆,亦不共用棧,進程由作業系統調度。
線程擁有自己獨立的棧和共用的堆,共用堆,不共用棧,線程亦由作業系統調度(標準線程是的)。
協程和線程一樣共用堆,不共用棧,協程由程式員在協程的代碼裡顯示調度。

協程的好處如下:
1.減少cpu線程環境切換的開銷
2.降低了記憶體消耗;
3.提高了cpu快取命中率;
4.整體上提高了效能;
5.不提高硬體的前提下,提升了系統的負載能力。

只需要極少的棧記憶體(大概是4~5KB),預設情況下,線程棧的大小為1MB,一個線程可以開啟數十萬的協程,線程佔用的記憶體開銷遠比協程要大得多。
golang原生就實現了協程,由runtime自行管理,一個go關鍵字就能開啟goroutine。簡直完美,但是今天要講的不是golang。

總之,協程就是便宜,廉價,高效的代名詞。

java裡面要擁護這種高效能的協程,要通過第三方包來實現quasar,comsat,kilim

上面這三位,就是目前所有java裡面能快速實現coroutines的jar。
quasar:通過織入java位元組碼的方式,改變位元組碼結果,來使用,javaagent的方式
comsat:quasar的封裝版本,提供輕量化的封裝能快速使用。
kilim:和quasar一樣,也要織入位元組碼來使用

但都有一個問題,必須預先給到註解,以上都能通過編譯,但是到了linux環境,需要通過javaagent,因位元組碼被改寫,無法追蹤具體問題。協程管理是個大問題,會被線程kill,無故消失,筆者通過大半個月的實驗,發現它們無法通過大部分環境,因而放棄。

kotlin.corouties

kotlin.corouties真是個非常好的api。文法簡化,可以和golang的go關鍵字有得一拼。但在目前的kotlin api中是實驗性質,不過已經具備上生產環境的能力,預計會在1.1.5中正式發布。因kotlin和java可以混編,所以coroutines是個下個高並發必備的知識點了。

kotlin.corouties調度器

CommonPool 調度器預設是通過fork/join的方式實現,目前還不提供介面,做自訂實現
launch(CommonPool)

Represents common pool of shared threads as coroutine dispatcher for compute-intensive tasks.
It uses[java.util.concurrent.ForkJoinPool]when available, which implements efficient work-stealing algorithm for its queues, so every coroutine resumption is dispatched as a separate task even when it already executes inside the pool.When available, it wraps ForkJoinPool.commonPool and provides a similar shared pool where not.

也就是說,kotlin的協程是並行調度的,關於fork/join也可以單獨開一章講了,暫不表。

Unconfined 調度器,預設是主線程調度 ,無限制啟動協程,一旦協程睡了或者掛了,會啟動新的協程

launch(Unconfined)

A coroutine dispatcher that is not confined to any specific thread.
It executes initial continuation of the coroutine right here in the current call-frame
and let the coroutine resume in whatever thread that is used by the corresponding suspending function, without
mandating any specific threading policy.

Note, that if you need your coroutine to be confined to a particular thread or a thread-pool after resumption,
but still want to execute it in the current call-frame until its first suspension, then you can use
an optional [CoroutineStart] parameter in coroutine builders like [launch] and [async] setting it to the
the value of [CoroutineStart.UNDISPATCHED].

ThreadPoolDispatcher.newSingleThreadContext調度器,單個線程的調度器

launch(newSingleThreadContext("MyOwnThread"))

Creates new coroutine execution context with the a single thread and built-in [yield] and [delay] support.
All continuations are dispatched immediately when invoked inside the thread of this context.
Resources of this pool (its thread) are reclaimed when job of this context is cancelled.
The specified [name] defines the name of the new thread.
An optional [parent] job may be specified upon creation.

launch(newFixedThreadPoolContext(100,"MyOwnThread")) 調度器,指定線程數量的調度器

Creates new coroutine execution context with the fixed-size thread-pool and built-in [yield] and [delay] support.
All continuations are dispatched immediately when invoked inside the threads of this context.
Resources of this pool (its threads) are reclaimed when job of this context is cancelled.
The specified [name] defines the names of the threads.
An optional [parent] job may be specified upon creation.

預設請全部使用launch(CommonPool),有特殊的限制問題,再考慮其他的調度器

launch(CommonPool) 非同步協程開啟

async(CommonPool) 同步協程開啟

官方樣本的Hello,World!,歡迎進入kotlin協程的世界

fun main(args: Array<String>) {    launch(CommonPool) { // create new coroutine in common thread pool        delay(1000L) // non-blocking delay for 1 second (default time unit is ms)        println("World!") // print after delay    }    println("Hello,") // main function continues while coroutine is delayed    Thread.sleep(2000L) // block main thread for 2 seconds to keep JVM alive}

kotlin中的sleep將不能暫停協程了,是個大坑,後面會講到。

launch 啟動協程,預設情況下直接開始執行,也可以顯式執行

var job= launch(CommonPool)   if(job.isActive){          job.cancel()       }else{            job.start()     }

job任務可以根據需要什麼時候開始執行,是否存活,取消等,提供了一系列api
有個小事,kotlin去掉了; 估計這個又可以引發一波大戰

CommonPool 調度器
delay將會暫停1秒協程運行,
printlin是kotlin的列印方法,等同於System.out.printlin
Thread.sleep 這句只能暫停啟動協程的線程

fun main(args: Array<String>) = runBlocking<Unit> {    val job = launch(CommonPool) { // create new coroutine and keep a reference to its Job        delay(1000L)        println("World!")    }    println("Hello,")    job.join() // wait until child coroutine completes}

runBlocking<Unit> 啟動一個非阻塞並且無傳回值的任務
job.join() 等待協程任務完成

fun main(args: Array<String>) = runBlocking<Unit> {    val job = launch(CommonPool) {            doWorld()     }    println("Hello,")    job.join()}// this is your first suspending functionsuspend fun doWorld() {    delay(1000L)    println("World!")}

這個講suspend 關鍵字,為的是代碼分離,不然就只能在 launch(CommonPool){}內部用delay來睡協程了,去掉了suspend是無法在其他方法調用delay睡協程了,直接編譯錯誤。

fun main(args: Array<String>) = runBlocking<Unit> {    val jobs = List(100_000) { // create a lot of coroutines and list their jobs        launch(CommonPool) {            delay(1000L)            print(".")        }    }    jobs.forEach { it.join() } // wait for all jobs to complete}

這個例子比較搞,啟動100K的協程,如果你像作者一樣,2G記憶體的渣機可能直接out-of-memory error,像筆者這樣的8G大記憶體,是沒有一點問題的。輕鬆愉快500ms執行完畢。

這個例子也是為了展示協程的輕量級和強悍,線程別說100K,就算10K,你的CPU和記憶體分分鐘炸了,只能重啟。

fun main(args: Array<String>) = runBlocking<Unit> {    val job = launch(CommonPool) {        var nextPrintTime = 0L        var i = 0        while (isActive) { // cancellable computation loop            val currentTime = System.currentTimeMillis()            if (currentTime >= nextPrintTime) {                println("I'm sleeping ${i++} ...")                nextPrintTime = currentTime + 500L            }        }    }    delay(1300L) // delay a bit    println("main: I'm tired of waiting!")    job.cancel() // cancels the job    delay(1300L) // delay a bit to see if it was cancelled....    println("main: Now I can quit.")}

delay的例子太多,單獨講一個。啟動了一個協程任務去計算當前的時間,然後你會發現協程內建了一個isActive屬性,這也是線程內部唯三的三大內建屬性之一。其他的兩個為context和coroutineContext,不過context已經被放棄了,大概是作者覺得context,詞不達意吧,從這點也可以發現kotlin不會隨意的刪除api,而是通過重新命名,重載的方式提供新的。

isActive:如果協程處於存活或任務未完成,狀態就返回true,如果取消或已完成,則返回false

例子的意思也很明顯告訴你如果任務在delay時間內未被cancel則一直計算下去並列印三次I'm sleeping,然後任務被cancel,協程取消。主線程輸出main: Now I can quit

fun main(args: Array<String>) = runBlocking<Unit> {    val job = launch(CommonPool) {        try {            repeat(1000) { i ->                println("I'm sleeping $i ...")                delay(500L)            }        } finally {            run(NonCancellable) {                println("I'm running finally")                delay(1000L)                println("And I've just delayed for 1 sec because I'm non-cancellable")            }        }    }    delay(1300L) // delay a bit    println("main: I'm tired of waiting!")    job.cancel() // cancels the job    delay(1300L) // delay a bit to ensure it was cancelled indeed    println("main: Now I can quit.")}

這個例子講的不可取消, run(NonCancellable)+finally=絕對執行的代碼
run(NonCancellable)協程內部啟動一個新的協程,並且不能取消,霸道總裁般的代碼
run...{}內可以使用coroutineContext,跟上一級的協程塊代碼做互動。

fun main(args: Array<String>) = runBlocking<Unit> {    withTimeout(1300L) {        repeat(1000) { i ->            println("I'm sleeping $i ...")            delay(500L)        }    }}

repeat(1000) :迭代器,輸入要迭代的次數:1000次
withTimeout(1300L) 時間1.3秒。
這裡講這個wiathTimeout主要是為了控制協程的逾時時間,避免協程,一直在活動。雖然便宜,不代表能讓任務一直執行下去,到了逾時的時間會直接拋出異常

suspend fun doSomethingUsefulOne(): Int {    delay(1000L) // pretend we are doing something useful here    return 13}suspend fun doSomethingUsefulTwo(): Int {    delay(1000L) // pretend we are doing something useful here, too    return 29}
fun main(args: Array<String>) = runBlocking<Unit> {    val time = measureTimeMillis {        val one = async(CommonPool, CoroutineStart.LAZY) { doSomethingUsefulOne() }        val two = async(CommonPool, CoroutineStart.LAZY) { doSomethingUsefulTwo() }        println("The answer is ${one.await() + two.await()}")    }    println("Completed in $time ms")}

三個例子
measureTimeMillis :傳回碼塊的執行耗時,比起java,話就是少,就是這麼屌

CoroutineStart:協程的執行模式(async和launch都可以用)

LAZY
懶載入

DEFAULT 預設的模式
預設 - 根據其上下文立即執行。

ATOMIC
根據其上下文原則(不可取消)計劃協調執行。
跟[DEFAULT]類似,但協程在開始執行前無法取消。

UNDISPATCHED
未指派:暫不明白用途

println("The answer is ${one.await() + two.await()}")
kotlin執行計算可在字串中一起計算
.await實際拿到的是協程返回的值,在例子中也就是13和29

suspend fun doSomethingUsefulOne(): Int {    delay(1000L) // pretend we are doing something useful here    return 20}suspend fun doSomethingUsefulTwo(): Int {    delay(1000L) // pretend we are doing something useful here, too    return 20}// The result type of asyncSomethingUsefulOne is Deferred<Int>fun asyncSomethingUsefulOne() = async(CommonPool) {    doSomethingUsefulOne()}// The result type of asyncSomethingUsefulTwo is Deferred<Int>fun asyncSomethingUsefulTwo() = async(CommonPool)  {    doSomethingUsefulTwo()}// note, that we don't have `runBlocking` to the right of `main` in this examplefun main(args: Array<String>) {    val time = measureTimeMillis {        // we can initiate async actions outside of a coroutine        val one = asyncSomethingUsefulOne()        val two = asyncSomethingUsefulTwo()        // but waiting for a result must involve either suspending or blocking.        // here we use `runBlocking { ... }` to block the main thread while waiting for the result        runBlocking {            println("The answer is ${one.await() + two.await()}")        }    }    println("Completed in $time ms")}

runBlocking{}是個同步非阻塞的代碼塊執行器,能統一拿到coroutines的傳回值,支援泛型和接受返回參,多個或單個協程一旦啟動後我們要拿傳回值不僅可以用await,也可以用runBlocking

      var result= runBlocking<Int> {            var resultint = one.await() + two.await()            println("The answer is resultint="+resultint)            //基本類型直接這樣寫就可以            resultint        }     println(result)

============================================================================

fun main(args: Array<String>) = runBlocking<Unit> {    val jobs = arrayListOf<Job>()    jobs += launch(Unconfined) { // not confined -- will work with main thread        println("      'Unconfined': I'm working in thread ${Thread.currentThread().name}")    }    jobs += launch(coroutineContext) { // context of the parent, runBlocking coroutine        println("'coroutineContext': I'm working in thread ${Thread.currentThread().name}")    }    jobs += launch(CommonPool) { // will get dispatched to ForkJoinPool.commonPool (or equivalent)        println("      'CommonPool': I'm working in thread ${Thread.currentThread().name}")    }    jobs += launch(newSingleThreadContext("MyOwnThread")) { // will get its own new thread        println("          'newSTC': I'm working in thread ${Thread.currentThread().name}")    }    jobs.forEach { it.join() }}

介紹了多個調度器

launch(Unconfined) launch(coroutineContext):這個調度器只有在runBlocking內部才能用,嚴格來說不算調度器,內部協程的下上文中,繼續啟動協程launch(CommonPool) launch(newSingleThreadContext("MyOwnThread")) 

具體解釋看開篇的說明

fun main(args: Array<String>) = runBlocking<Unit> {    val jobs = arrayListOf<Job>()    jobs += launch(Unconfined) { // not confined -- will work with main thread        println("      'Unconfined': I'm working in thread ${Thread.currentThread().name}")        delay(500)        println("      'Unconfined': After delay in thread ${Thread.currentThread().name}")    }    jobs += launch(coroutineContext) { // context of the parent, runBlocking coroutine        println("'coroutineContext': I'm working in thread ${Thread.currentThread().name}")        delay(1000)        println("'coroutineContext': After delay in thread ${Thread.currentThread().name}")    }    jobs.forEach { it.join() }}

println(" 'Unconfined': After delay in thread ${Thread.currentThread().name}")
這一句將會在新的協程中列印出來,因為協程本身被delay了

private val log = LoggerFactory.getLogger(X::class.java)fun log(msg: String) = println("[${Thread.currentThread().name}] $msg")fun main(args: Array<String>) = runBlocking<Unit> {    val a = async(coroutineContext) {        log.info("I'm computing a piece of the answer")        log("I'm computing a piece of the answer")        6    }    val b = async(coroutineContext) {        log.info("I'm computing another piece of the answer")        log("I'm computing a piece of the answer")        7    }    log.info("The answer is ${a.await() * b.await()}")}

這裡要講的是日誌:如果你是lombok的使用者,那麼很遺憾,lombox現在暫不支援在kotlin使用@Slf4j或者@Log4j

fun log(msg: String) = println("[${Thread.currentThread().name}] $msg")
這一句是官方樣本給的,最好別用

private val log = LoggerFactory.getLogger(X::class.java)

跟以前一樣用LoggerFactory拿就好了

fun main(args: Array<String>) = runBlocking<Unit> {    println("My job is ${coroutineContext.get(Job.Key)}")    println("My job is ${coroutineContext.get(Job)}")    println("My job is ${coroutineContext[Job]}")}

runBlocking<Unit> 這個老夥計了,老夥計本身其實也是coroutines啟動的沒想到吧,驚不驚喜,意不意外。這種設計就跟golang一樣,有個統一的runtime管理器,但這裡是顯式的。
它被設計出來最大的原因就是阻塞執行了,在它內部可以啟動多個async協程,然後共同計算出一個複雜的對象,然後統一返回給runBlocking,外部就可以直接接收

今天就聊到這,delay一下。歡迎加群交流關於kotlin.coroutines

QQ群:641163584

下一章,應該是下周

轉載請聯絡我本人授權

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.