序引:什么是进程和线程
直白地讲,进程就是应用程序的启动实例。比如我们运行一个游戏,打开一个软件,就是开启了一个进程。
进程拥有代码和打开的文件资源、数据资源、独立的内存空间。
线程又是什么呢?线程从属于进程,是程序的实际执行者。一个进程至少包含一个主线程,也可以有多个子线程。同时,线程拥有自己的栈空间。
总结起来就是:
对操作系统来说,进程是CPU进行资源分配和管理的最小单元,线程是CPU调度任务执行的最小单元。
影子:什么是协程
在解释“什么是协程”之前,我们先说点题外话:
网上很多文章也用这个例子,也用这个官方例子来说明使用协程的优势,然后就说协程是什么轻量级的线程,又是什么用户态的,协程像线程但又不是线程,通过什么禅让机制,禅让线程执行权,大幅度提升CPU效率,诸如此类,简直就是胡说八道,误人子弟。
好了,牢骚发完,我们现在回归正题,解释下上图截图的代码情况:
官网这个例子就是通过repeat函数启动了10000个协程,然后它让我们试一试使用Thread来实现会发生什么,也就是像下面这样:
repeat(100_000) {
thread {
Thread.sleep(1000L)
print (".")
}
}
这个例子我们不用跑也知道大概会发生什么了,创建100000个线程。。。
但是,我想说的是,kotlin官方用这个例子真的有点不厚道了,用java底层的Thread类,和他们造出来的一个基于Thread类封装的“工具包”进行对比。
真正要比的话,我们用java的Executor和他比比?
repeat(100_000) {
val executor = Executors.newSingleThreadScheduledExecutor()
val task = Runnable {
print(".")
}
repeat (100_00) {
executor.schedule(task, 1, TimeUnit.SECONDS)
}
}
我用上面这段程序跑了一下,用协程相对于java的线程池,并没有发现什么实质上的性能优势。感兴趣的也可以自己借助Android Studio Profiler试一试
所以,我们可以得出一个结论:
使用Kotlin协程,本质上其实并没有比我们原先的开发模式有多大性能上的优势,因为我们所使用的的OkHttp、AsyncTask等内部都帮我们封装了线程池,而不是直接使用Thread类。
那么Kotlin协程运行在单线程里面吗?
百度 Kotlin协程,找到比较高赞,阅读量高达数万的一篇文章,文章中截取了各种概念,到网上找了各种和协程相关的图,但我想说,这其实是误人子弟!
很简单,我们做一个小实验就能知道结果:
//在没有开启协程前,先打印一下进程名称和进程id
println("Main: " + "threadName = " + Thread.currentThread().name + " threadId = " + Thread.currentThread().id)
// 循环20次
repeat(20) {
GlobalScope.launch {
// 开启协程后,先打印一下进程名称和进程id
println("IO: " + "threadName = " + Thread.currentThread().name + " threadId = " + Thread.currentThread().id)
delay(1000L)
}
}
Thread.sleep(100)
AndroidStudio执行结果:
发现了什么?所谓的协程完全就是开启了一个新的线程来执行任务,上面我们20个任务,实际上协程只是开启了4个线程,然后在后面的任务中,重复的使用这4个线程,直至任务完成。这是不是跟java中的线程池逻辑几乎完全一致???
我们再来看看上面的代码在IntelliJ IDEA里面的执行结果:
依然是为每隔新的任务创建子线程,在子线程完成任务,这跟Java的线程调用是完全一样的。至于IntelliJ IDEA里面的执行结果与AndroidStudio稍有不一样,是因为kotlin语言对Android平台下的协程做了优化,以更适应Android运营环境。
看到这里,是不是有点颠覆你原来对协程的认识?难道网上的所有文章都是错误的?
其实网上大部分文章说的协程,指的可能都是其他语言的协程的特点,比如Go、Lua、Python等等。而我们要学的,是Kotlin协程,它不是真正意义上的协程,它也没有那么的神秘,只要你没有魔改JVM,start了几个线程,操作系统就会创建几个线程,根本谈不上什么性能更好,CPU效率更高,别被忽悠到了。
好了,现在我们再来回答上面的问题——什么是协程:
1、协程就是可以由程序自行控制挂起(暂停执行)、恢复(继续在原来暂停的地方执行)的程序——这是核心;
2、他可以用来实现多任务的协作执行;
3、解决异步任务控制流的灵活转移,简化程序逻辑复杂度,看起来更简洁舒适;
4、简单来说协程就是让我们能够用同步的思维开发异步和并发程序的一个框架。**
协程的作用
1、协程可以让异步和并发的逻辑代码同步化;
2、可以降低异步并发程序的设计复杂度;
3、要注意的是:协程本身并不能让我们的代码异步,只是让我们的异步逻辑变得更简单。
4、有效避免常规Android中网络请求的地狱式回调。
var user = api.getUser()//异步的网络请求(异步在子线程)
tvName.text = user.name//更新UI(UI主线程)
以及:
coroutineScope.launch(Dispatchers.Main) { // 开始协程:主线程
val token = api.getToken() // 网络请求:IO 线程
val user = api.getUser(token) // 网络请求:IO 线程
nameTv.text = user.name // 更新 UI:主线程
}
上面getToken() 和getUser(token)两次网络请求,顺序执行,但是需要第一个请求成功后,再利用请求结果发起第二个请求,这样的话,不可避免的会出现:逻辑实现上的异步、请求嵌套以及请求回调,想想都头大。但是协程很好的帮我们很好的解决了这个问题
Kotlin的协程对Thread相关的API做了一套封装,让我们不用过多的关心线程问题就可以方便的实现高并发和异步操作,这就是Kotlin下的“协程”。
实际上kotlin的协程最大的作用和优势在于:你可以把运行在不同线程的异步代码写在同一个代码块里,甚至忽略异步或者说并发这件事情的存在——异步逻辑同步思维来实现!
协程架构
协程学习三部曲
协程的概念:
由程序自行控制挂起(暂停执行)、恢复(继续在原来暂停的地方执行)的程序
协程要素:
就是指kotlin协程标准函数库里面的各个函数