Kotlin+协程+FLow+Channel,实现生产消费者模式3种案例

生成者消费模式,在设计中也是一种频繁的应用,掌握它便能轻松应对各种场景

一、前言

生产消费者模式在Andoird 里面应用还相当广泛,它带来的好处大致可以分为:

  1. 解耦:能彻底解耦生产模块和消费模块,生产者和消费者之间不直接进行交互,而是通过一个缓冲区(如队列或缓冲区)来进行交互。这种方式减少了生产者和消费者之间的直接依赖关系,使得系统的各个部分更加独立,易于维护和扩展‌
  2. 同时,它也能平衡生产消费之间的速度。在多线程环境中,生产者和消费者的处理速度往往是不一致的。生产者-消费者模式通过缓冲区来平衡这种速度差异,生产者只需将数据放入缓冲区,而消费者从缓冲区取出数据处理。这样,生产者不需要等待消费者处理完数据再继续生产,反之亦然,从而提高了系统的整体效率‌
  3. 提高系统稳定性和效率‌:通过缓冲区的使用,系统可以在生产者速度过快或消费者速度过慢时进行调节,避免了直接的生产者-消费者间的等待,减少了系统负载,提高了程序的稳定性和效率‌
  4. 应用广泛 ‌:生产者-消费者模式在许多系统中都有应用:
    Android系统中的Handler,Looper, message,MessageQueue‌ 它们的运行模式就是一种生成消费者模式。
    同样:在Android直播应用中 :我们把每一帧的数据编码好,准备成传输数据包: RTMPPackage 加入到缓冲队列里面,这个过程就是数据生成阶段,另一边我们不停地从缓冲队列里面取出 传输数据包: RTMPPackage,推送给服务端,这个过程就是消费阶段。
  • 这里需要注意的,生产消费者模式并不是23种设计模式中的。

  • 本文主要介绍:Kotlin+协程+FLow+Channel,实现生产消费者模式几种案例

二、案例一,传统式写法:

  • 生产者向缓冲区产生数据,当缓冲区超过设置最大数时候,让其等待,等待消费者处理完了,再加入进去。
  • 当消费者发现缓冲区为空时候,等待,知道有可以消费的数据时候为止。
  • 关键点:使用while循环检查条件避免虚假唤醒,notifyAll确保唤醒正确线程
kotlin 复制代码
val buffer = LinkedList<Int>()
val MAX_SIZE = 5
val lock = Object()

// 生产者
fun produce(item: Int) {
    synchronized(lock) {
        while (buffer.size >= MAX_SIZE) {
            lock.wait()  // 缓冲区满时等待
        }
        buffer.add(item)
        lock.notifyAll() // 必须使用notifyAll避免死锁
    }
}

// 消费者
fun consume(): Int {
    synchronized(lock) {
        while (buffer.isEmpty()) {
            lock.wait()  // 缓冲区空时等待
        }
        val item = buffer.removeFirst()
        lock.notifyAll()
        return item
    }
}

// 测试代码
fun main() {
    repeat(3) { i ->
        Thread { produce(i) }.start() // 启动3个生产者
    }
    repeat(2) { 
        Thread { println(consume()) }.start() // 启动2个消费者
    }

从上面我们可以看出,最经典的,是完全自由的,可完全自定义的,涉及到缓存,最大值,锁相关知识,以及如果 异步 也需要自己来实现管理处理。这样写起来需要基本功深才行。

三、案例二、协程+Channel方式实现

下面示例:

我们创建了容量为5的缓冲通道,

生产者每100ms发送数据,

消费者每200ms处理数据,

自动实现流量控制。

当缓冲区满时send挂起,

空时receive挂起

同时,由于我们协程中,我们可以通过直接通过协程中的 Dispatchers来直接切换线程,让其异步中实现生产和消费。

scss 复制代码
fun main() = runBlocking {
    // 创建容量为5的缓冲通道
    val channel = Channel<Int>(capacity = 5)
    
    // 生产者协程
    val producer = launch {
        repeat(10) { i ->
            delay(100) // 模拟生产耗时
            channel.send(i)
            println("生产: $i (缓冲区剩余: ${channel.capacity - channel.size})")
        }
        channel.close() // 生产完成后关闭通道
    }

    // 消费者协程
    val consumer = launch {
        channel.consumeEach { item ->
            delay(200) // 模拟消费耗时
            println("消费: $item")
        }
    }

    // 等待生产和消费完成
    joinAll(producer, consumer)
}

四、案例三、协程结合Flow方式实现

如下面代码所示:

我们通过flow构建生产者流,buffer控制背压,onEach处理消费逻辑,flowOn实现线程切换 涉及到的核心点:

  1. 背压策略选择
    buffer():缓冲指定数量元素,默认溢出策略为挂起生产者
    conflate():仅保留最新元素,适合实时状态更新场景
    collectLatest():新元素到达时取消当前消费任务

  2. 线程调度控制
    flowOn(Dispatchers.IO):指定上下游执行线程池

    生产者与消费者默认共享协程上下文,需显式分离避免阻塞

  3. 异常处理增强

    可通过Flow的.catch统一处理生产阶段和消费阶段的异常。

scss 复制代码
fun producer(): Flow<Int> = flow {
    repeat(10) { i ->
        delay(100) // 模拟生产耗时
        emit(i)
        println("生产: $i")
    }
}.catch{
    e -> println("生产异常: $e")
}
.flowOn(Dispatchers.IO) // 指定生产者协程上下文

fun main() = runBlocking {
    producer()
        .buffer(5) // 设置缓冲区容量
        .onEach { item ->
            delay(200) // 模拟消费耗时
            println("消费: $item")
        }.catch{
            e -> println("生产异常: $e")
        }.flowOn(Dispatchers.IO) // 指定消费者协程上下文
        .collect()
}

从上面我们也可以看出:

Flow与Channel方案对比

特性 Flow方案 Channel方案
数据生成方式 冷流(按需生产) 热流(独立于消费)
背压处理 通过操作符(buffer/conflate)控制 依赖通道容量设置
异常处理 统一处理 需要手动分别处理
适用场景 纯数据流处理 需要双向通信的场景

五、总结

本文重点介绍了,传统生产消费者模式和 Kotlin+协程+FLow+Channel实现消费者模式

  1. 传统的完全自定义,涉及到相关内容较多,需要基础扎实
  2. 基于协程和Flow或者协程+Channel来实现,可以很简单的背压处理,切换线程处理。使用起来更加方便。

感谢阅读:

欢迎用你发财的小手 关注,点赞、收藏

这里你会学到不一样的东西

相关推荐
阿华的代码王国6 分钟前
【Android】适配器与外部事件的交互
android·xml·java·前端·后端·交互
一杯科技拿铁21 分钟前
提升 LLM 推理效率的秘密武器:LM Cache 架构与实践
架构·llm
跨界混迹车辆网的Android工程师1 小时前
实现Android图片手势缩放功能的完整自定义View方案,结合了多种手势交互功能
android·交互
wyjcxyyy1 小时前
打靶日记-PHPSerialize
android
云边有个稻草人2 小时前
KingbaseES:一体化架构与多层防护,支撑业务的持续稳定运行与扩展
架构·国产数据库
安卓开发者12 小时前
Android RxJava 组合操作符实战:优雅处理多数据源
android·rxjava
阿华的代码王国12 小时前
【Android】RecyclerView复用CheckBox的异常状态
android·xml·java·前端·后端
一条上岸小咸鱼12 小时前
Kotlin 基本数据类型(三):Booleans、Characters
android·前端·kotlin
Jerry说前后端12 小时前
RecyclerView 性能优化:从原理到实践的深度优化方案
android·前端·性能优化
喂完待续13 小时前
Apache Hudi:数据湖的实时革命
大数据·数据仓库·分布式·架构·apache·数据库架构