1. 前言

关于协程,可能大家最经常听到的一句话就是“协程是轻量级的线程”。一脸懵逼,有没有?这可是官方的slogan,严格意义上讲,一方面官方是想让大家把协程和线程产生一个直观关联,另一方面想宣传协程在性能上比线程更优,充分地说服大家去使用它。本文我将尝试把协程是什么讲明白。

2. 聊聊线程

既然说“协程是轻量级的线程”。那我们有必要先回顾下线程是什么? 在泛Java程序中,要启动一个线程那太easy了,new一个Thread,重写run方法,调用start方法,就是这么简单。

public fun thread(start: Boolean = true,isDaemon: Boolean = false,contextClassLoader: ClassLoader? = null,name: String? = null,priority: Int = -1,block: () -> Unit
): Thread {val thread = object : Thread() {public override fun run() {block()}}if (isDaemon)thread.isDaemon = trueif (priority > 0)thread.priority = priorityif (name != null)thread.name = nameif (contextClassLoader != null)thread.contextClassLoader = contextClassLoaderif (start)thread.start()return thread
}

简单是简单,不过也有不少弊端呢:

  1. 如果创建的线程数量超过了最大文件描述符数量,程序会报OOM的(当创建的线程的速度>线程消耗的速度时)

  2. 如果需要频繁创建线程去执行耗时非常短的代码,频繁的切换线程对性能也是有影响的

  3. 线程之间的通信比较复杂,把A线程的数据传递到B线程不那么容易

因为有了以上弊端,于是我们有了线程池。

3. 聊聊线程池

由于本文重点是讲协程,如果有同学对线程池不了解可以适当补补课,网上资料很多也不难。

线程池想必大部分同学都很熟悉了。缓存池,对象池,连接池,各种池相关的技术就是缓存技术。线程池缓存的对象就是线程。

public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue) {this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,Executors.defaultThreadFactory(), defaultHandler);
}

我们可以看到线程池几个核心参数:

  1. corePoolSize核心线程池数量
  2. maximumPoolSize最大线程池数量
  3. BlockingQueue<Runnable> workQueue 工作队列,工作队列中保存的是Runnable对象

接下来再看下工作线程Worker的源码,它继承自Thread,它的run方法调用了runWorker方法,源码如下:

//ThreadPoolExecutor.java
final void runWorker(Worker w) {Thread wt = Thread.currentThread();Runnable task = w.firstTask;w.firstTask = null;w.unlock(); // allow interruptsboolean completedAbruptly = true;try {while (task != null || (task = getTask()) != null) {w.lock();// If pool is stopping, ensure thread is interrupted;// if not, ensure thread is not interrupted.  This// requires a recheck in second case to deal with// shutdownNow race while clearing interruptif ((runStateAtLeast(ctl.get(), STOP) ||(Thread.interrupted() &&runStateAtLeast(ctl.get(), STOP))) &&!wt.isInterrupted())wt.interrupt();try {beforeExecute(wt, task);Throwable thrown = null;try {task.run();} catch (RuntimeException x) {thrown = x; throw x;} catch (Error x) {thrown = x; throw x;} catch (Throwable x) {thrown = x; throw new Error(x);} finally {afterExecute(task, thrown);}} finally {task = null;w.completedTasks++;w.unlock();}}completedAbruptly = false;} finally {processWorkerExit(w, completedAbruptly);}
}

我们看到该方法主要就是循环从workQueue中拿取可执行的runnable去执行。细心的同学可能会提出疑问了,如果while循环的条件不成立,那岂不是会导致线程直接退出。这种想法其实是个误区了,由于workQueue是BlockingQueue,如果队列中没有runnable对象,此处代码是会阻塞的,跳不出循环。

那么回到文章开头,“协程是轻量级的线程”,到底何物比线程还要轻量级。对了聪明的读者可能已经猜出来了,workQueue中的runnable。为了方便理解,我们可以把协程理解为线程执行的最小单位,工作队列中的Runnable,有源码为证。

代码来自kotlinx-coroutines-core-jvm:1.4.1
//1. AbstractCoroutine
public abstract class AbstractCoroutine<in T> (...): JobSupport(active),
Job, Continuation<T>, CoroutineScope //2. DispatchedContinuation
internal class DispatchedContinuation<in T>(@JvmField val dispatcher: CoroutineDispatcher,@JvmField val continuation: Continuation<T>
) : DispatchedTask<T>(MODE_UNINITIALIZED), CoroutineStackFrame, Continuation<T> by continuation//3. DispatchedTask
internal abstract class DispatchedTask<in T>(@JvmField public var resumeMode: Int
) : SchedulerTask() internal actual typealias SchedulerTask = Task//4.Task
internal abstract class Task(@JvmField var submissionTime: Long,@JvmField var taskContext: TaskContext
) : Runnable {constructor() : this(0, NonBlockingContext)inline val mode: Int get() = taskContext.taskMode // TASK_XXX
}

上述源码也可以简化为

public abstract class AbstractCoroutine<in T>
(...): Runnable

简单讲协程就是一个Runnable,而且这个Runnable必须是存储在工作队列中,才能发挥它轻量级的优势。

题外话:线程,死循环,队列,MessageQueue。Android开发者最熟悉的MainThread不正天然的满足这些特性吗。难道说往Handler中post一个Runnable也是启动一个协程吗?如果这样类比能够让你更容易理解协程,那就这样理解吧,这样理解也没问题。只不过协程能做的远比往主线程post一个线程最小单位多多了。

既然线程池,MainThread已经充分地发挥了线程的性能。那么为什么还要有协程呢?协程在他们之上又解决了什么问题呢?

4. 聊聊协程

首先来看一个最简单的例子,在Activity中开启一个协程,然后在子线程中休眠10s,结束后在主线程中打印出子线程中返回的值。

//TestActivity.java
MainScope().launch {val result = withContext(Dispatchers.IO) {Thread.sleep(10_000)println("I am running in ${Thread.currentThread()}")"Hello coroutines"}println("I am running in ${Thread.currentThread()} result is $result")
}

打印结果如下,我们看到在子线程中睡眠,在主线程中打印子线程中返回的值。

2021-11-22 22:29:02.868 3407-3463/com.peter.viewgrouptutorial I/System.out:
I am running in Thread[DefaultDispatcher-worker-1,5,main]2021-11-22 22:29:02.874 3407-3407/com.peter.viewgrouptutorial I/System.out:
I am running in Thread[main,5,main] result is Hello coroutines

咋一看,大家可能会有疑问了,老兄,实现这种需求,有必要这么复杂吗,老弟我三下五除二搞定好吗?看我的:

thread {Thread.sleep(10_000)println("I am running in ${Thread.currentThread()}")val result = "Hello coroutines"Handler(Looper.getMainLooper()).post {println("I am running in ${Thread.currentThread()} result is $result")}
}

轻轻松松几行代码搞定,稳重而且不失风度,打印结果一模一样。

2021-11-22 22:35:59.016 3597-3655/com.peter.viewgrouptutorial I/System.out:
I am running in Thread[Thread-3,5,main]2021-11-22 22:35:59.020 3597-3597/com.peter.viewgrouptutorial I/System.out:
I am running in Thread[main,5,main] result is Hello coroutines

那么问题来了,如果需求是在子线程中睡眠10s,将返回值返回给另一个子线程呢?当然用传统的线程也不是不能实现,如果用协程那就相当简单了

// 为了模拟出效果,特意使用只有一个线程的线程池来当Dispatcher
MainScope().launch(Executors.newFixedThreadPool(1).asCoroutineDispatcher()) {val result = withContext(Dispatchers.IO) {Thread.sleep(10_000)println("I am running in ${Thread.currentThread()}")"Hello coroutines"}println("I am running in ${Thread.currentThread()} result is $result")
}

打印结果如下,注意看是两个不同的线程

2021-11-22 22:41:01.953 3872-3927/com.peter.viewgrouptutorial I/System.out:
I am running in Thread[DefaultDispatcher-worker-1,5,main]2021-11-22 22:41:01.960 3872-3926/com.peter.viewgrouptutorial I/System.out:
I am running in Thread[pool-1-thread-1,5,main] result is Hello coroutines

5. 总结

所以在我看来,协程有以下几个特性:

  1. 将协程体封装成线程可执行的最小单位Runnable,准确讲是协程中的Continuation,通过分发机制分发到对应的线程对应的工作队列中
  2. Continuation会保存协程栈帧中的数据,在切换线程时把协程栈帧带过去,在切回线程时,又通过它把数据带回来。(没错,类似callback机制)
  3. 线程池对开发者封装了线程,只需要往里面submit Runnable就可以了。而协程同时对开发者封装了线程和Callback,开发者无需关心线程和线程切换的内在逻辑。
//TestActivity.java
MainScope().launch {val result = withContext(Dispatchers.IO) {Thread.sleep(10_000)println("I am running in ${Thread.currentThread()}")"Hello coroutines"}println("I am running in ${Thread.currentThread()} result is $result")
}
 val coroutinesBodyRunnable = java.lang.Runnable {thread {Thread.sleep(10_000)println("I am running in ${Thread.currentThread()}")val result = "Hello coroutines"Handler(Looper.getMainLooper()).post {println("I am running in ${Thread.currentThread()} result is $result")}}}Handler(Looper.getMainLooper()).post(coroutinesBodyRunnable)

以上代码是等价的。时间原因,具体原理,后续再讲,敬请期待。如果觉得文章有帮助,帮我分享给周围的朋友吧。期待我们可以在评论中碰撞出更多的火花,一起探讨技术,一起进步。

记得关注“字节小站”同名公众号哟~

抽丝剥茧聊Kotlin协程之协程与线程之间的区别相关推荐

  1. android 主线程调用,Android 主线程和线程之间相互发送消息

    通过分析Activity源码,我们知道每个Activity都有一个Looper,所以主线程在接收Message是不需要调用Looper.prepare()和Looper.loop(),但是线程是不带L ...

  2. Python协程之协程在手,说走就走

    协程在手,说走就走 什么是协程 先介绍–生产者-消费者模式 解释如下 生产者消费者模式并不是GOF提出的23种设计模式之一,23种设计模式都是建立在面向对象的基础之上的,但其实面向过程的编程中也有很多 ...

  3. 管程,进程及线程之间的区别

    1,首先我们先了解进程.线程.管程各自的概念: 进程:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动.它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基 ...

  4. 协程与线程, 进程的区别

    进程.线程.协程的区别 进程是资源分配的单位,真正执行代码的是线程,操作系统真正调度的是线程. 进程没有线程效率高,进程占用资源多,线程占用资源少,比线程更少的是协程. 协程依赖于线程.线程依赖于进程 ...

  5. python协程和线程_线程和协程之间的区别

    线程和协程之间的区别很大,甚至大过进程和线程之间的区别.线程建立在进程之上,协程建立在线程之上.那么协程是什么呢? 协程是一段计算机程序,它一般是一个协作类型的子程序,执行时允许暂停和恢复.协程非常适 ...

  6. python协程和线程区别_python中的线程和协程之间有什么区别

    一.首先我们来了解一下线程和协程的概念1.线程线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源( ...

  7. unity协程和线程

    谈谈线程和协程的区别.一般应用一个应用程序只使用线程这一"资源". 需要明确,Unity只使用了一个线程,但是,我们需要"同时做很多事",那Unity作为单线程 ...

  8. Kotlin协程之Dispatchers原理

    文章目录 前置知识 demo startCoroutineCancellable 小结 Kotlin协程不是什么空中阁楼,Kotlin源代码会被编译成class字节码文件,最终会运行到虚拟机中.所以从 ...

  9. python协程详解_对Python协程之异步同步的区别详解

    一下代码通过协程.多线程.多进程的方式,运行代码展示异步与同步的区别. import gevent import threading import multiprocessing # 这里展示同步和异 ...

最新文章

  1. 16个概念带你入门 Kubernetes
  2. 数据科学家所需的大脑训练
  3. 重磅!Spring Boot 2.5.0火热发布,还学得动吗?
  4. python numpy.meshgrid() 函数的用法(快速生成坐标矩阵)
  5. Newtonsoft.Json 获取匿名类数据
  6. [Android][Android Studio] *.jar 与 *.aar 的生成与*.aar导入项目方法
  7. echarts中国地图3D各个城市标点demo
  8. 理解 JS 回调函数中的 this
  9. 【CTF/MISC】图片隐写题(binwalk/foremost/010editer配合使用)
  10. 计算机人工智能专业大一新生入学前做点什么
  11. 【20G】三菱PLC全套资料(手册+视频教程+编程软件+仿真软件)
  12. 迁移学习 Transfer Learning(可能是目前最全的迁移学习资料库?)
  13. 什么是数据库的存储过程?
  14. Java 8 新特性
  15. 亿级流量电商详情页系统的大型高并发与高可用缓存架构实战 目录
  16. openlayers结合谷歌api进行地图定位
  17. CANoe.DiVa 操作指南 - 时间参数配置
  18. 众里寻他千百度-百度发展的秘密
  19. 财务报表分析实务(第三讲)
  20. 【lizhi125】Cameyo - 简单几步轻松制作单文件绿色版软件!

热门文章

  1. CAD打印设置(com接口)
  2. win10计算机屏幕暗怎么办,遇到win10电脑屏幕亮度忽明忽暗的情况应该怎么办
  3. 开源全文搜索引擎MeiliSearch
  4. Linux入门笔记-尚硅谷韩顺平-基础篇实操篇
  5. C++ : switch:switch string的两种用法
  6. 取消文件夹git同步
  7. 【boost搜索引擎】
  8. java 数字范围正则_Java 正则表达式
  9. android 3d引擎_手机资讯:?iPhone XS 采用的 3D 结构光技术与安卓手机 TOF 技术有什么不同...
  10. Xcode - 直接截取手机的屏幕图片,并保存到电脑