Kotlin 之 协程(四)协程并发,2024年最新面试必问hr的问题

无限制通道(Unlimited channel)是最接近队列的模拟:生产者能够将元素发送到此通道,而且它将无限增加。 send方法将永远不会被挂起。 若是没有更多的内存,则会抛出OutOfMemoryException。 和队列不一样的是当使用者尝试从空通道接收消息并被挂起直到有一些新元素发送到该通道时继续使用。

Buffered channel

缓冲通道(Buffered channel)的大小受指定数字的限制。 生产者能够将元素发送到此通道,直到达到最大限制。 全部元素都在内部存储。 通道已满时,下一个send呼叫将被挂起,直到出现更多可用空间。

Rendezvous channel

"约定"通道(Rendezvous channel)是没有缓冲区的通道。 这与建立大小为零的缓冲通道(Buffered channel)相同。 其中一个功能(send或receive)始终被挂起,直到调用另外一个功能为止。 若是调用了send函数,但消费者没有准备好处理该元素则receive会挂起,而且send也会被挂起。 一样,若是调用了receive函数且通道为空,换句话说,没有准备好发送该元素的的send被挂起-receive也会被挂起。

Conflated channel

发送到合并通道( Conflated channel)的新元素将覆盖先前发送的元素,所以接收方将始终仅能获取最新元素。 send调用将永远不会被挂起。

建立通道时,指定其类型或缓冲区大小(若是须要缓冲的通道):


val rendezvousChannel = Channel<String>()

val bufferedChannel = Channel<String>(10)

val conflatedChannel = Channel<String>(CONFLATED)

val unlimitedChannel = Channel<String>(UNLIMITED)



默认状况下,会建立一个"约定"通道(Rendezvous channel)。

在如下示例中,将建立一个"约定"通道,两个生产者协程和一个消费者协程:


import kotlinx.coroutines.channels.Channel

import kotlinx.coroutines.*



fun main() = runBlocking<Unit> {

    val channel = Channel<String>()

    launch {

        channel.send("A1")

        channel.send("A2")

        log("A done")

    }

    launch {

        channel.send("B1")

        log("B done")

    }

    launch {

        repeat(3) {

            val x = channel.receive()

            log(x)

        }

    }

}



fun log(message: Any?) {

    println("[${Thread.currentThread().name}] $message")

}



以上将会打印以下结果:



[main @coroutine#4] A1

[main @coroutine#4] B1

[main @coroutine#2] A done

[main @coroutine#3] B done

[main @coroutine#4] A2



channel实际上是一个队列,队列中一定存在缓冲区,这个缓冲区满了并且一直没有人调用receive取走函数,send就需要挂起,故意让接收端的节奏放慢,发现send总是被挂起,直到receive之后才会继续往下执行。


    fun run1() {

        val channel = Channel<Int>(Channel.UNLIMITED)

        //Channel协程间通信,并发安全的队列

        runBlocking {

            //生产者

            val p = launch {

                for (i in 1..5) {

                    channel.send(i)

                    log("send = $i")

                }

            }

            //消费者

            val c = launch {

                //正常接收数据

                while (true) {

                    //故意让接收端的节奏放慢,发现send总是被挂起,直到receive之后才会继续往下执行

                    delay(2000)

                    val el = channel.receive()

                    log("re = $el")

                }

                //通过迭代器iterator接收数据

                //val iterator = channel.iterator()

                //while (iterator.hasNext()) {

                //    delay(2000)

                //    log("iterator = ${iterator.next()}")

                //}



            }



            joinAll(p,c)

        }



    }



打印:

com.z.zjetpack V/zx: send = 1

com.z.zjetpack V/zx: send = 2

com.z.zjetpack V/zx: send = 3

com.z.zjetpack V/zx: send = 4

com.z.zjetpack V/zx: send = 5

com.z.zjetpack V/zx: re = 1

com.z.zjetpack V/zx: re = 2

com.z.zjetpack V/zx: re = 3

com.z.zjetpack V/zx: re = 4

com.z.zjetpack V/zx: re = 5



produce 与actor


  • 构造生产者与消费者的便捷方法

  • 我们可以通过produce方法启动一个生产者协程,并返回一个reveive channel,其他协程就可以用这个channel来接收数据了。反过来我们可以用actor启动一个消费者协程。


    fun run2(){

       runBlocking {

           //快捷创建生产者协程,返回一个接收Channel

          val receiveChannel = produce<Int> {

               repeat(5){

                   delay(1000)

                   send(it)

               }

          }



           val job2 = launch {

               for (i in receiveChannel) {

                   log("receiveChannel = $i")

               }

           }

           job2.join()

       }



        runBlocking {

            //构造消费者的便捷方法

            val sendChannel = actor<Int> {

                while (true) {

                    val re = receive()

                    log("re = $re")

                }

            }

           val p =  launch {

                for (i in 1..3) {

                    sendChannel.send(i)

                }



            }

            p.join()

        }

    }



打印:

com.z.zjetpack V/zx: receiveChannel = 0

com.z.zjetpack V/zx: receiveChannel = 1

com.z.zjetpack V/zx: receiveChannel = 2

com.z.zjetpack V/zx: receiveChannel = 3

com.z.zjetpack V/zx: receiveChannel = 4

com.z.zjetpack V/zx: re = 1

com.z.zjetpack V/zx: re = 2

com.z.zjetpack V/zx: re = 3



channel的关闭


  • produce和actor返回的channel都会随着对应的协程执行完毕而关闭,也正式这样,channel才会被称为热数据流.

  • 对于一个channel,如果我们调用了它的close方法,它会立即停止接收新元素,它的isClosedForSend会立即返回true,由于channel缓冲区的存在,可能还有一些元素没有被处理完,所以要等所有元素都被读取之后isClosedForReceive才会返回true

  • channel的生命周期最好由主导方来维护,建议由主导的一方实现关闭。


    fun run3(){

        runBlocking {

            val channel = Channel<Int>(3)

            //生产者

            launch {

                List(3){

                    channel.send(it)

                    log("send = $it")

                }



                channel.close()

                log("isClosedForSend = ${channel.isClosedForSend}")

                log("isClosedForReceive = ${channel.isClosedForReceive}")

            }



            //消费者

            launch {

                for (c in channel) {

                    log("re = $c")

                    delay(1000)

                }



                log("消费isClosedForSend = ${channel.isClosedForSend}")

                log("消费isClosedForReceive = ${channel.isClosedForReceive}")

            }



        }

    }



打印:

com.z.zjetpack V/zx: send = 0

com.z.zjetpack V/zx: send = 1

com.z.zjetpack V/zx: send = 2

com.z.zjetpack V/zx: isClosedForSend = true

com.z.zjetpack V/zx: isClosedForReceive = false

com.z.zjetpack V/zx: re = 0

com.z.zjetpack V/zx: re = 1

com.z.zjetpack V/zx: re = 2

com.z.zjetpack V/zx: 消费isClosedForSend = true

com.z.zjetpack V/zx: 消费isClosedForReceive = true



BroadcastChannel


发送端和接收端在channel中存在一对多的场景,虽然有多个接收端,但是同一个元素只会被一个接收端读取到,广播则不同,多个接收端不存在互斥行为。

在这里插入图片描述


    fun run4() {

        runBlocking {

            //直接创建

            // val broadcastChannel = BroadcastChannel<Int>(Channel.BUFFERED)

            //broadcast方法创建

            val channel = Channel<Int>()

            val broadcastChannel = channel.broadcast(Channel.BUFFERED)

            //创建3个协程来接收

            List(3) {

                launch {

                    val receiveChannel = broadcastChannel.openSubscription()

                    for (r in receiveChannel) {

                        log("协程 $it, re = $r")

                    }

                }

            }

            launch {

                List(3) {

                    broadcastChannel.send(it)

                }

                broadcastChannel.close()

            }



        }

    }



打印:

com.z.zjetpack V/zx: 协程 0, re = 0

com.z.zjetpack V/zx: 协程 0, re = 1

com.z.zjetpack V/zx: 协程 0, re = 2

com.z.zjetpack V/zx: 协程 1, re = 0

com.z.zjetpack V/zx: 协程 1, re = 1

com.z.zjetpack V/zx: 协程 1, re = 2

com.z.zjetpack V/zx: 协程 2, re = 0

com.z.zjetpack V/zx: 协程 2, re = 1

com.z.zjetpack V/zx: 协程 2, re = 2



多路复用


复用多个await

两个api分别从网络和本地获取数据,期望哪个先返回就先用哪个做显示

在这里插入图片描述


    fun CoroutineScope.getFromLocal() = async {

        delay(1000)

        "返回本地数据"

    }



    fun CoroutineScope.getFromNet() = async {

        "返回网络数据"

    }



    fun run5() {

        runBlocking {

            launch {

                val local = getFromLocal()

                val net = getFromNet()



                val res = select<String> {

                    local.onAwait { it }

                    net.onAwait { it }

                }



                log("值 = $res")

            }.join()



        }

    }



打印:

com.z.zjetpack V/zx: 值 = 返回网络数据



复用多个channel

跟await类似,会接收到最快的那个channel消息


    fun run6() {

        runBlocking {

            val channels = listOf(Channel<Int>(), Channel<Int>())

            launch {

                delay(100)

                channels[0].send(1)

            }



            launch {

                delay(500)

                channels[1].send(5)

            }



            val result = select<Int> {

                channels.forEach { re ->

                    re.onReceive{it}

                }

            }



            log("result = $result")

        }

    }

打印:

com.z.zjetpack V/zx: result = 1




**自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。**

**深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!**

**因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。**
![img](https://img-blog.csdnimg.cn/img_convert/1919b0bbff3394460260d6195f482895.png)
![img](https://img-blog.csdnimg.cn/img_convert/9503483300cca7e9821b5e021fc7b7bc.png)
![img](https://img-blog.csdnimg.cn/img_convert/0aa43301df94fa00dea3e70e860e7116.png)
![img](https://img-blog.csdnimg.cn/img_convert/8f20a775cce58b428d8c86764935b200.png)
![img](https://img-blog.csdnimg.cn/img_convert/d156107c384d15ae452f8f10942f59fd.png)
![img](https://img-blog.csdnimg.cn/img_convert/162764e6f31ac0c13c031cd6ebe75ecb.png)
![img](https://img-blog.csdnimg.cn/13f2cb2e05a14868a3f0fd6ac81d625c.png)

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!**

**由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新**

**如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)**
![img](https://img-blog.csdnimg.cn/img_convert/191e2c011bf97ec6eb82229a955b1e58.png)



### 写在最后

今天关于面试的分享就到这里,还是那句话,有些东西你不仅要懂,而且要能够很好地表达出来,能够让面试官认可你的理解,例如Handler机制,这个是面试必问之题。有些晦涩的点,或许它只活在面试当中,实际工作当中你压根不会用到它,但是你要知道它是什么东西。

最后在这里小编分享一份自己收录整理上述技术体系图相关的几十套**腾讯、头条、阿里、美团等公司的面试题**,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含**知识脉络 + 诸多细节**,由于篇幅有限,这里以图片的形式给大家展示一部分。

还有 **高级架构技术进阶脑图、Android开发面试专题资料**,高级进阶架构资料 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。

**【Android核心高级技术PDF文档,BAT大厂面试真题解析】**

![](https://img-blog.csdnimg.cn/img_convert/59634b2219066b76af49fe908fda28dd.webp?x-oss-process=image/format,png)

**【算法合集】**

![](https://img-blog.csdnimg.cn/img_convert/33c40ef02baf08572a9eb11ebb4e2fe4.webp?x-oss-process=image/format,png)

**【延伸Android必备知识点】**

![](https://img-blog.csdnimg.cn/img_convert/2b19c8ef4f7488941f68ed83725d2626.webp?x-oss-process=image/format,png)



**一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**
![img](https://img-blog.csdnimg.cn/img_convert/4fc7bdd55cdc39330991a629de635f54.png)

### 写在最后

今天关于面试的分享就到这里,还是那句话,有些东西你不仅要懂,而且要能够很好地表达出来,能够让面试官认可你的理解,例如Handler机制,这个是面试必问之题。有些晦涩的点,或许它只活在面试当中,实际工作当中你压根不会用到它,但是你要知道它是什么东西。

最后在这里小编分享一份自己收录整理上述技术体系图相关的几十套**腾讯、头条、阿里、美团等公司的面试题**,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含**知识脉络 + 诸多细节**,由于篇幅有限,这里以图片的形式给大家展示一部分。

还有 **高级架构技术进阶脑图、Android开发面试专题资料**,高级进阶架构资料 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。

**【Android核心高级技术PDF文档,BAT大厂面试真题解析】**

[外链图片转存中...(img-TMORtlRU-1712797462947)]

**【算法合集】**

[外链图片转存中...(img-zaNb4WQi-1712797462948)]

**【延伸Android必备知识点】**

[外链图片转存中...(img-EzqRSdVl-1712797462948)]



**一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**
[外链图片转存中...(img-2gx2vM6e-1712797462948)]
  • 17
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值