Android面试题之Kotlin协程到底是什么?它是线程吗?

简介: 本文探讨了协程与线程的区别,指出协程并非线程,而是轻量级的线程替代。协程轻量体现在它们共享调用栈,内存占用少,仅需几个KB。协程切换发生在用户态,避免了昂贵的内核态切换。在Kotlin中,协程通过Continuation对象实现上下文保存,允许高效并发执行,而不会像线程那样消耗大量资源。通过`runBlocking`和`launch`示例展示了协程的非阻塞挂起特性。总结来说,协程的轻量主要源于内存占用少、切换开销低和高并发能力。

本文首发于公众号“AntDream”,欢迎微信搜索“AntDream”或扫描文章底部二维码关注,和我一起每天进步一点点

协程是什么?

协程是线程吗?是线程池的线程?是轻量级的线程?

实际上,可以非常肯定的说,协程不是线程!

我们反过来想,如果协程是线程,那么咱们怎么让一个线程挂起呢?

那既然协程不是线程,那又为什么常说协程是轻量级的线程呢?协程轻在哪呢?

"轻量级"的线程

Kotlin中的协程经常被称为“轻量级线程”,这是相对于传统的线程模型而言的。为了更好地理解这一点,我们需要从内存占用、任务切换、JVM内存模型等多方面进行剖析。

1. 轻量级的原因

1.1 内存占用

  • 线程: 每个线程在创建时分配一定数量的栈内存(默认大约1MB)。如果系统启动大量线程,则会消耗大量内存,可能导致系统资源枯竭。
  • 协程: 协程是运行在现有线程中的,它们不需要单独的栈内存,而是共享调用栈。这样协程仅需要少量内存开销,通常每个协程只占用几个KB。这使得同一线程可以管理和运行大量协程,不受传统线程数量限制。

1.2 任务切换

  • 线程切换: 线程切换由操作系统管理,涉及到用户态和内核态之间的切换,代价较高,需要保存和恢复CPU寄存器、程序计数器、内存栈等。
  • 协程切换: 协程切换在用户态完成,不涉及内核态切换,只是切换函数的上下文,代价相对低很多。

2. 内存模型

2.1 线程内存模型

在JVM中,每个线程都有自己独立的线程栈,每个栈帧包含局部变量、操作数栈和相关信息。当线程被挂起时,所有这些信息必须保存并在重新调度时恢复。

2.2 协程内存模型

协程的栈帧通常是堆上的对象,当协程挂起时,不需要切换线程,只是函数调用的上下文发生变化,把协程状态保存到堆中。这种模型使得内存利用更加高效和灵活。

2.3 协程堆栈帧

协程在挂起时,会将当前的堆栈帧转换为对象并存储在堆中。这个对象包含了所有当前帧的局部变量、挂起点以及其他必要信息。恢复时,这个对象重新转换为堆栈帧并继续执行。

2.4 Continuation

Kotlin中的挂起函数实质上会被编译器转换成带有回调的 Continuation 对象。该对象包含两个主要部分:

  • 上下文(Continuation Context):绑定的执行环境。
  • 恢复逻辑(Resume Logic):保存和处理挂起点的逻辑。
interface Continuation<in T> {
    val context: CoroutineContext
    fun resumeWith(result: Result<T>)
}

3. 执行机制

3.1 线程

线程由操作系统调度,执行时获得CPU时间片,可能被抢占。这一过程完全由操作系统管理,且每次线程切换都会导致上下文切换,引入显著的开销。

3.2 协程

协程仅占用一个线程的部分时间,是由协程库(例如 kotlinx.coroutines)管理。一个线程可以执行多个协程,只要它们异步工作并时常挂起和恢复。这大大减少了切换开销,改善性能。

4. 协程例子

import kotlinx.coroutines.*

fun main() = runBlocking {
    repeat(100_000) {
        launch {
            delay(1000L)
            println("Coroutine $it is done")
        }
    }
}

在这个例子中,启动了10万协程,但内存占用远远小于10万个线程,且切换效率高。这是因为:

  • 非阻塞挂起: delay 使得协程挂起,但不阻塞线程,使得同一线程可以继续处理其他协程。
  • 上下文保存和恢复: 协程的上下文切换只需要保存和恢复堆上的对象状态,代价低。

由于协程不阻塞线程,上面的例子中,日志几乎是同时打印的

总结

Kotlin 协程的,主要原因包括:

  1. 内存占用更少:协程不需要独立的栈内存,而是共享调用栈。
  2. 低切换开销:协程切换在用户态完成,无需与操作系统交互,开销小。
  3. 高并发模型:在同一线程上可以高效地运行大量协程,不受传统线程创建管理的限制。

欢迎关注我的公众号AntDream查看更多精彩文章!

目录
相关文章
|
监控 Kubernetes Java
阿里面试:5000qps访问一个500ms的接口,如何设计线程池的核心线程数、最大线程数? 需要多少台机器?
本文由40岁老架构师尼恩撰写,针对一线互联网企业的高频面试题“如何确定系统的最佳线程数”进行系统化梳理。文章详细介绍了线程池设计的三个核心步骤:理论预估、压测验证和监控调整,并结合实际案例(5000qps、500ms响应时间、4核8G机器)给出具体参数设置建议。此外,还提供了《尼恩Java面试宝典PDF》等资源,帮助读者提升技术能力,顺利通过大厂面试。关注【技术自由圈】公众号,回复“领电子书”获取更多学习资料。
|
安全 Java 程序员
面试必看:如何设计一个可以优雅停止的线程?
嘿,大家好!我是小米。今天分享一篇关于“如何停止一个正在运行的线程”的面试干货。通过一次Java面试经历,我明白了停止线程不仅仅是技术问题,更是设计问题。Thread.stop()已被弃用,推荐使用Thread.interrupt()、标志位或ExecutorService来优雅地停止线程,避免资源泄漏和数据不一致。希望这篇文章能帮助你更好地理解Java多线程机制,面试顺利! 我是小米,喜欢分享技术的29岁程序员。欢迎关注我的微信公众号“软件求生”,获取更多技术干货!
414 53
|
数据采集 Java Linux
面试大神教你:如何巧妙回答线程优先级这个经典考题?
大家好,我是小米。本文通过故事讲解Java面试中常见的线程优先级问题。小明和小华的故事帮助理解线程优先级:高优先级线程更可能被调度执行,但并非越高越好。实际开发需权衡业务需求,合理设置优先级。掌握线程优先级不仅能写出高效代码,还能在面试中脱颖而出。最后,小张因深入分析成功拿下Offer。希望这篇文章能助你在面试中游刃有余!
306 4
面试大神教你:如何巧妙回答线程优先级这个经典考题?
|
Java 程序员 开发者
Java社招面试题:一个线程运行时发生异常会怎样?
大家好,我是小米。今天分享一个经典的 Java 面试题:线程运行时发生异常,程序会怎样处理?此问题考察 Java 线程和异常处理机制的理解。线程发生异常,默认会导致线程终止,但可以通过 try-catch 捕获并处理,避免影响其他线程。未捕获的异常可通过 Thread.UncaughtExceptionHandler 处理。线程池中的异常会被自动处理,不影响任务执行。希望这篇文章能帮助你深入理解 Java 线程异常处理机制,为面试做好准备。如果你觉得有帮助,欢迎收藏、转发!
863 14
|
安全 Java 程序员
Java 面试必问!线程构造方法和静态块的执行线程到底是谁?
大家好,我是小米。今天聊聊Java多线程面试题:线程类的构造方法和静态块是由哪个线程调用的?构造方法由创建线程实例的主线程调用,静态块在类加载时由主线程调用。理解这些细节有助于掌握Java多线程机制。下期再见! 简介: 本文通过一个常见的Java多线程面试题,详细讲解了线程类的构造方法和静态块是由哪个线程调用的。构造方法由创建线程实例的主线程调用,静态块在类加载时由主线程调用。理解这些细节对掌握Java多线程编程至关重要。
721 13
|
算法 安全 Java
Java线程调度揭秘:从算法到策略,让你面试稳赢!
在社招面试中,关于线程调度和同步的相关问题常常让人感到棘手。今天,我们将深入解析Java中的线程调度算法、调度策略,探讨线程调度器、时间分片的工作原理,并带你了解常见的线程同步方法。让我们一起破解这些面试难题,提升你的Java并发编程技能!
741 16
|
缓存 安全 Java
面试中的难题:线程异步执行后如何共享数据?
本文通过一个面试故事,详细讲解了Java中线程内部开启异步操作后如何安全地共享数据。介绍了异步操作的基本概念及常见实现方式(如CompletableFuture、ExecutorService),并重点探讨了volatile关键字、CountDownLatch和CompletableFuture等工具在线程间数据共享中的应用,帮助读者理解线程安全和内存可见性问题。通过这些方法,可以有效解决多线程环境下的数据共享挑战,提升编程效率和代码健壮性。
489 6
|
编译器 Android开发 开发者
Android经典面试题之Kotlin中Lambda表达式和匿名函数的区别
Lambda表达式和匿名函数都是Kotlin中强大的特性,帮助开发者编写简洁而高效的代码。理解它们的区别和适用场景,有助于选择最合适的方式来解决问题。希望本文的详细讲解和示例能够帮助你在Kotlin开发中更好地运用这些特性。
456 9
|
安全 Java 程序员
面试直击:并发编程三要素+线程安全全攻略!
并发编程三要素为原子性、可见性和有序性,确保多线程操作的一致性和安全性。Java 中通过 `synchronized`、`Lock`、`volatile`、原子类和线程安全集合等机制保障线程安全。掌握这些概念和工具,能有效解决并发问题,编写高效稳定的多线程程序。
552 11
|
Java Linux 调度
硬核揭秘:线程与进程的底层原理,面试高分必备!
嘿,大家好!我是小米,29岁的技术爱好者。今天来聊聊线程和进程的区别。进程是操作系统中运行的程序实例,有独立内存空间;线程是进程内的最小执行单元,共享内存。创建进程开销大但更安全,线程轻量高效但易引发数据竞争。面试时可强调:进程是资源分配单位,线程是CPU调度单位。根据不同场景选择合适的并发模型,如高并发用线程池。希望这篇文章能帮你更好地理解并回答面试中的相关问题,祝你早日拿下心仪的offer!
556 6

热门文章

最新文章