深入学习Kotlin协程(一)-协程是什么?

Kotlin协程是什么?

协程并不是Kotlin提出的,其他的一些编程语言在早期就已经实现了协程,比如: Go、Python、Lua等语言。

当我们查看Kotlin官方文档时,可以看到一句很醒目的话

本质上,协程是轻量级的线程。

我们把这句话拆分一下通过例子理解会透彻很多,我们尝试将上述这句话拆分为两个单词:轻量级线程

线程

首先我们都知道什么是线程,并且怎么创建一个线程,这节课我们不了解什么是线程,我们看看Kotlin的协程为什么被称为线程

我们沿用官方demo看看

fun coroutineTest() {
    repeat(100_000) {
        GlobalScope.launch {
            delay(10000L)
            println("World i=$it threadName=${Thread.currentThread().name}, threadId=${Thread.currentThread().id}")
        }
        println("World i=$it")
    }
    Thread.sleep(10000L)
}

fun main() {
    coroutineTest()
}

打印一下部分日志


World i=97490 threadName=DefaultDispatcher-worker-1, threadId=11
World i=97491 threadName=DefaultDispatcher-worker-1, threadId=11
...
World i=97492 threadName=DefaultDispatcher-worker-9, threadId=19
...
World i=96774 threadName=DefaultDispatcher-worker-10, threadId=20
World i=96756 threadName=DefaultDispatcher-worker-12, threadId=22
World i=97496 threadName=DefaultDispatcher-worker-12, threadId=22
World i=97531 threadName=DefaultDispatcher-worker-12, threadId=2
...
World i=97621 threadName=DefaultDispatcher-worker-11, threadId=21
World i=97656 threadName=DefaultDispatcher-worker-11, threadId=21
...
World i=96749 threadName=DefaultDispatcher-worker-3, threadId=13
World i=97675 threadName=DefaultDispatcher-worker-3, threadId=13
...
World i=96724 threadName=DefaultDispatcher-worker-2, threadId=12
World i=96731 threadName=DefaultDispatcher-worker-2, threadId=12
...
World i=96721 threadName=DefaultDispatcher-worker-4, threadId=14
World i=96733 threadName=DefaultDispatcher-worker-4, threadId=14
Process finished with exit code 0

因为我们有十万条日志,所以上述日志适当性的筛选了一些

通过日志可以看出,使用Kotlin协程创建的Worker线程是可以复用的,这也就解释了Kotlin协程是线程的意义,本质上Kotlin协程就是创建了一个可以复用的线程,注意是可以复用的线程.那如果创建一个不可以复用的线程会出现什么情况?

这就是Kotlin官方提出的一个比较,官方文档如下

如果你首先将 GlobalScope.launch 替换为 thread,编译器会报以下错误:

Error: Kotlin: Suspend functions are only allowed to be called from a coroutine or another suspend function

这是因为 delay 是一个特殊的 挂起函数 ,它不会造成线程阻塞,但是会 挂起 协程,并且只能在协程中使用。

我们用Thread替换GlobalScope.launch创建一个十万个线程,看看到底会发生什么?

fun threadTest() {
    repeat(100_0000) {
        Thread {
            sleep(10000L)
            println("World it = $it threadName=${Thread.currentThread()}, threadId=${Thread.currentThread().id}")

        }.start()
        println("threadId it = $it")
    }
    Thread.sleep(10000L)
}
fun main() {
    threadTest()
}

我们还是打印一下输出

...
threadId it = 6921
threadId it = 6922
threadId it = 6923
threadId it = 6924
threadId it = 6925
threadId it = 6926
threadId it = 6927
Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread
    at java.lang.Thread.start0(Native Method)
    at java.lang.Thread.start(Thread.java:717)
    at com.bennyhuo.kotlin.coroutinebasics.kevin.CoroutineTestKt.threadTest(CoroutineTest.kt:49)
    at com.bennyhuo.kotlin.coroutinebasics.kevin.CoroutineTestKt.main(CoroutineTest.kt:68)
    at com.bennyhuo.kotlin.coroutinebasics.kevin.CoroutineTestKt.main(CoroutineTest.kt)

报错日志也一目了然,当创建第6928个线程时报错了,错误的提示是我们不能创建新的线程了,也就是线程数达到顶峰了,这是为什么呢?

主要原因就是Thread创建的是单线程,不可复用的线程,内存是有限定的,这个值不是固定的,跟手机性能等都有关系(猜想,不对的大佬可以纠正)。

另外一个重要的原因跟协程的delay()有关,我们可以看看kotlin协程官网说法

这是因为 delay 是一个特殊的 挂起函数 ,它不会造成线程阻塞,但是会 挂起 协程,并且只能在协程中使用。

协程实现中我们使用了 非阻塞的 delay(……) 而线程实现中使用了 阻塞的 Thread.sleep(……)。这也是thread创建到第6928个线程后报错的部分原因。

现在,我们知道了Kotlin为什么被称为线程,我们再来看看为什么被称作轻量级线程

轻量级

轻量级的字面意思是:小巧,方便,简单,那根据字面意思,我们是不是可以这么理解:

协程是一种更简单、方便、轻巧的线程呢?

我们还是用常用的一个网络请求来说明一下

我们用okhttp+retrofit实现一个简单的网络请求,接口如下:

interface GitHubApi {
    @GET("users/{login}")
    fun getUserCallback(@Path("login") login: String): Call<User>

    @GET("users/{login}")
    suspend fun getUserSuspend(@Path("login") login: String): User
}

data class User(val id: String, val name: String, val url: String)

Retrofit初始化如下:

val gitHubServiceApi by lazy {
    val retrofit = retrofit2.Retrofit.Builder()
            .baseUrl("https://api.github.com")
            .addConverterFactory(GsonConverterFactory.create())
            .build()

    retrofit.create(GitHubServiceApi::class.java)
}

我们请求网络时:

gitHubServiceApi.getUserCallback("bennyhuo").enqueue(object : Callback<User> {
    override fun onFailure(call: Call<User>, t: Throwable) {  //成功
        handler.post { showError(t) }
    }

    override fun onResponse(call: Call<User>, response: Response<User>) {  //失败
        handler.post { response.body()?.let(::showUser) ?: showError(NullPointerException())}
    }
})

以上代码就是我们经常用来实现网络请求的步骤,好点的话会用rxjava观察者模式实现

我们现在用协程改造一下请求网络部分:

GlobalScope.launch(Dispatchers.Main) {
    try {
        showUser(gitHubServiceApi.getUserSuspend("bennyhuo").await())  //成功
    } catch (e: Exception) {
        showError(e) //失败
    }
}

这里需要注意一下:要使用协程我们需要升级Retrofit版本大于2.6.0,并且在GitHubApi中增加suspend关键字

如上代码,我们先不管是什么是Globalcope、suspend以及Dispatchers,我们只看如上代码,是不是比普通方式实现的简单,从代码看出,我们不再用回调的形式实现,而是直接在try内部执行了我们的showUser操作,这种实现是不是比用原始的回调或rxjava的观察者模式实现要简单的多

小结

我们用两段代码搞清楚了kotlin协程的官方定义:协程是轻量级线程,简单来说Kotlin协程就是Kotlin给我们提供的一套:简单、方便、轻巧的线程库,能帮助我们快速的实现网络请求,简化代码逻辑。

既然协程这么好,那他到底有哪些作用呢?可以查看第二篇文章:深入学习Kotlin协程序二-协程的作用。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容