当前位置: 首页 > news >正文

使用 Koltin 集合时容易产生的 bug 注意事项

来看下面代码:

class ChatManager {private val messages = mutableListOf<Message>()/*** 当收到消息时回调*/fun onMessageReceived(message: Message) {messages.add(message)}/*** 当删除消息时回调*/fun onMessageDeleted(message: Message) {messages.remove(message)}/*** 当消息成功发送到服务器时回调*/fun onMessageDeliveryStateChanged(messageId: String, state: DeliveryState) {val messageIndex = messages.indexOfFirst { it.id == messageId }if (messageIndex >= 0) {val message = messages[messageIndex]messages[messageIndex] = message.copy(deliveryState = state)}}
}data class Message(val id: String,val content: String,val senderId: String,val receiverId: String,val deliveryState: DeliveryState
)
enum class DeliveryState { UNDELIVERED, SENT, DELIVERED }

上面代码中 ChatManager 持有一个 mutableListOf 类型的属性成员 messagesChatManager 主要负责在接收消息、删除消息、发送消息时对消息状态进行管理。

我们思考一下,这个代码有什么问题呢?

如果你只是在单线程/主线程中调用这个ChatManager 类的相关方法,那么不会有任何问题,但是假如你在多个线程中调用这个类,比如在线程池中跑,那就不一定了。

想必你大概已经猜到了,出现问题的原因就是多个线程的情况下,不同的线程调用不同的方法对 messages 进行操作可能导致资源竞争,因此这里有潜在的并发安全问题。

举个例子,假如在多线程环境下我们有以下代码:

val chatManager = ChatManager()
...
chatManager.onMessageDeleted(message) // Thread1 正在访问这一行
...
// 同时,Thread2 正在访问这一行
chatManager.onMessageDeliveryStateChanged("abc", DeliveryState.DELIVERED) 

这时会有什么问题呢?

在这里插入图片描述

假设程序按照上图标注的顺序执行, messages 集合列表中此时共有 [A, B, C, D, E] 5个消息对象,那么线程 2 首先查询到 index = 3 的消息(也就是D),此时线程 1 同时在执行 onMessageDeleted 方法,删除了消息 D ,这之后,线程 2 开始进入 if 代码块执行,此时线程 2 并不知道有其他人修改了 messages 集合,那么它会按照 index = 3 来取出消息并修改它的状态,但是由于消息列表中的 D 被线程 1 删除了,列表变成 [A, B, C, E] ,因此这时线程 2 取到的index = 3的消息会是 E,那么结果就是本应该修改 D 的状态却阴差阳错地修改了 E 的状态!这就很要命了!

还没有完,假如 messages 集合列表只有 [A, B, C, D] 4个消息,同样按照上面的逻辑分析你会发现线程 2 这时取不到 index = 3 的消息了,因为被线程 1 删除了一个,消息列表不够 4 个了,这种情况下,你的应用可能会得到某种类似于 IndexOutOfBoundsException 的异常信息,如果你没有捕获处理异常,那么恭喜你,你的应用此时崩溃了!

所以,如果你没有意识到集合类可能在多线程下导致的并发安全问题,一旦产生这样的bug或异常,就会很棘手,很难发现问题的原因。

有人可能会想到,既然 MutableList 有问题,那么我用不可变的 List 不就可以了(严格说是只读的),于是代码可能会修改成下面这样:

class ChatManagerFixed {private var messages = listOf<Message>()/*** 当收到消息时回调*/fun onMessageReceived(message: Message) {messages += message}/*** 当删除消息时回调*/fun onMessageDeleted(message: Message) {messages -= message}/*** 当消息成功发送到服务器时回调*/fun onMessageDeliveryStateChanged(messageId: String, state: DeliveryState) {messages = messages.map { message ->if (message.id == messageId) {message.copy(deliveryState = state)} else message}}
}

注意,messages += messagemessages -= message 这样的操作每次都会产生一个新的 List 对象,就像 Java 的 String 类那样,每次操作都会产生一个新的不可变String 对象,这样应该没有问题了吧?

但实际上这个代码仍然存在并发安全隐患,问题就在于 messages += message,它其实等价于下面代码:

messages = messages + message

很明显,这不是一个原子操作,涉及到 messages 变量的一次读操作和 messages 变量的一次写操作。假设有多个线程同时执行这段代码,依然会存在同步问题:

fun onMessageReceived(message: Message) {// List is initially []// Thread 1 adds "Message 1"// Thread 2 adds "Message 2"// Expected: ["Message 1", "Message 2"]// If thread 1 finishes first, the list will be ["Message 1"]// If thread 2 finishes first, the list will be ["Message 2"]messages = messages + message
}

如上面代码注释所示,如果 List 初始为空,有 2 个线程同时往里面添加消息,那么可能结果不会按照我们的预期那样。

一旦理解了问题所在,解决办法就很简单了,从 Java 过来的我们肯定有着解决并发问题的丰富经验,比如最简单的就是使用 Kotlin 提供的同步工具 synchronized 函数:

class ChatManagerFixed {private val lock = Any()private var messages = listOf<Message>()/*** 当收到消息时回调*/fun onMessageReceived(message: Message) {synchronized(lock) {messages += message}}/*** 当删除消息时回调*/fun onMessageDeleted(message: Message) {synchronized(lock) {messages -= message}}/*** 当消息成功发送到服务器时回调*/fun onMessageDeliveryStateChanged(messageId: String, state: DeliveryState) {synchronized(lock) {messages = messages.map { message ->if (message.id == messageId) {message.copy(deliveryState = state)} else message}}}
}

当然,如果你喜欢用 MutableList ,也是一样的解决方式:

class ChatManagerFixed {private val lock = Any()private val messages = mutableListOf<Message>()/*** 当收到消息时回调*/fun onMessageReceived(message: Message) {synchronized(lock) {messages.add(message)}}/*** 当删除消息时回调*/fun onMessageDeleted(message: Message) {synchronized(lock) {messages.remove(message)}}/*** 当消息成功发送到服务器时回调*/fun onMessageDeliveryStateChanged(messageId: String, state: DeliveryState) {synchronized(lock) {val messageIndex = messages.indexOfFirst { it.id == messageId }if (messageIndex >= 0) {val message = messages[messageIndex]messages[messageIndex] = message.copy(deliveryState = state)}}}
}

可以看到这个问题的解决并非难事,非常简单,困难的是如何发现这种问题,如果没有并发安全的意识,可能只能对着应用抛出的异常日志发呆而无从下手。

如果你使用 Kotlin 协程,在 Kotlin 协程中也提供了一些相应的并发工具,如 MutexSemaphore等,感兴趣的可以参考我的另一篇文章:【深入理解Kotlin协程】协程中的Channel和Flow & 协程中的线程安全问题

相关文章:

使用 Koltin 集合时容易产生的 bug 注意事项

来看下面代码&#xff1a; class ChatManager {private val messages mutableListOf<Message>()/*** 当收到消息时回调*/fun onMessageReceived(message: Message) {messages.add(message)}/*** 当删除消息时回调*/fun onMessageDeleted(message: Message) {messages.r…...

CKA认证,开启您的云原生之旅!

在当今数字化时代&#xff0c;云计算已经成为企业和个人发展的关键技术。而获得CKA&#xff08;Certified Kubernetes Administrator&#xff09;认证&#xff0c;将是您在云原生领域迈出的重要一步。 CKA认证是由Kubernetes官方推出的权威认证&#xff0c;它旨在验证您在Kuber…...

基于springboot+vue的抗疫物资管理系统(前后端分离)

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、阿里云专家博主、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战&#xff0c;欢迎高校老师\讲师\同行交流合作 ​主要内容&#xff1a;毕业设计(Javaweb项目|小程序|Pyt…...

nebula容器方式安装:docker 安装nebula到windows

感谢阅读 基础环境安装安装docker下载nebula 安装数据库命令行安装查询network nebula-docker-compose_nebula-net并初始化查询安装初始使用root&#xff08;God用户类似LINUX的root&#xff09; 关闭服务 安装UI 基础环境安装 安装docker 点我下载docker 下载nebula 数据…...

干洗行业上门预约解决方案,干洗店洗鞋店小程序开发;

互联网干洗店洗鞋店小程序,企业干洗方案,干洗行业小程序,上门取衣小程序,预约干洗小程序,校园干洗店小程序,工厂干洗店小程序,干洗店小程序开发&#xff1b; 一、干洗店洗鞋店小程序核心功能介绍: 1.(支持上门取送、送货到店、寄存网点、智能衣柜四种下单方式) 用户下单-上门取…...

【Spring Boot 3】【JPA】@ManyToOne 实现一对多单向关联

【Spring Boot 3】【JPA】@ManyToOne 实现一对多单向关联 背景介绍开发环境开发步骤及源码工程目录结构总结背景 软件开发是一门实践性科学,对大多数人来说,学习一种新技术不是一开始就去深究其原理,而是先从做出一个可工作的DEMO入手。但在我个人学习和工作经历中,每次学…...

Mathematica学习笔记收纳

笔记 可以关注官方公众号 帮助文件 https://reference.wolfram.com/language/index.html.zh 南京大学的介绍 https://oi.nju.edu.cn/Mathematica/listm.htm...

java反射高级用列(脱敏+aop)

ClassUtils 、FieldUtils、MethodUtils、ReflectionUtils高级 List<String> list = new ArrayList<>(); Class<?> userClass = ClassUtils.getUserClass(list.getClass()); System.out.println(Collection.class.isAssignableFrom(userClass)); Class<?…...

C++函数对象包装器function类详解

函数对象包装器是对函数的封装&#xff0c;为函数对象提供一个容器&#xff0c;一个封装。C中现有的可调用实体的一种类型安全的包装&#xff08;相对来说&#xff0c;函数指针的调用不是类型安全的&#xff09;&#xff0c;换句话说&#xff0c;函数对象包装器就是函数的容器。…...

SpringMVC 学习(八)之文件上传与下载

目录 1 文件上传 2 文件下载 1 文件上传 SpringMVC 对文件的上传做了很好的封装&#xff0c;提供了两种解析器。 CommonsMultipartResolver&#xff1a;兼容性较好&#xff0c;可以兼容 Servlet3.0 之前的版本&#xff0c;但是它依赖了 commons-fileupload …...

《低功耗方法学》翻译——附录A:睡眠晶体管设计

附录A&#xff1a;睡眠晶体管设计 休眠晶体管是PMOS或NMOS高VT晶体管&#xff0c;用于在待机模式下关闭设计部件的电源。PMOS休眠晶体管用于切换VDD电源&#xff0c;因此被称为“header开关”。NMOS休眠晶体管控制VSS电源&#xff0c;因此被称为“footer开关”。在90 nm及以下…...

How to implement multiple file uploads based on Swagger 3.x in Spring boot 3.x

How to implement multiple file uploads based on Swagger 3.x in Spring boot 3.x Projectpom.xmlOpenAPIConfigFileUploadControllerapplication.yaml Project pom.xml <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://…...

spring boot 集成科大讯飞星火认知大模型

首先到官网https://console.xfyun.cn/services/aidoc申请key 一、安装依赖 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance&…...

springboot/ssm高校宣讲会管理系统Java企业招聘宣讲系统web

演示视频&#xff1a;https://www.bilibili.com/video/BV1vz421R7cg/、 基于springboot(可改ssm)vue项目 开发语言&#xff1a;Java 框架&#xff1a;springboot/可改ssm vue JDK版本&#xff1a;JDK1.8&#xff08;或11&#xff09; 服务器&#xff1a;tomcat 数据库&am…...

2024.02.23作业

1. 尝试处理普通信号 #include "test.h"#define MAXSIZE 128void handler(int signo) {if (SIGINT signo){printf("用户按下了 ctrl c 键\n");} }int main(int argc, char const *argv[]) {if (signal(SIGINT, SIG_IGN) SIG_ERR){perror("signal …...

倒模专用制作耳机壳UV树脂:改性丙烯酸树脂

倒模专用制作耳机壳的UV树脂是经过改性的丙烯酸树脂&#xff0c;具有高透明度、高粘度、快速固化的特点。这种树脂可以通过紫外线光固化&#xff0c;快速形成坚硬的表面&#xff0c;并且具有较高的硬度和耐磨性&#xff0c;因此非常适合用于制作耳机壳。 此外&#xff0c;改性丙…...

chatgpt:还有哪些人工智能和科技值得关注?

今天&#xff0c;很多人的目光都被ChatGPT吸引&#xff0c;其实&#xff0c;人工智能的范围很大&#xff0c;远不止ChatGPT或者其他自然语言的处理工具。所以说不管ChatGPT的结果如何&#xff0c;人工智能依然是未来。 那么在ChatGPT之外&#xff0c;还有没有什么值得关注的人…...

LeetCode 2997.使数组异或和等于K的最少操作次数

给你一个下标从 0 开始的整数数组 nums 和一个正整数 k 。 你可以对数组执行以下操作 任意次 &#xff1a; 选择数组里的 任意 一个元素&#xff0c;并将它的 二进制 表示 翻转 一个数位&#xff0c;翻转数位表示将 0 变成 1 或者将 1 变成 0 。 你的目标是让数组里 所有 元素…...

计算机设计大赛 深度学习大数据物流平台 python

文章目录 0 前言1 课题背景2 物流大数据平台的架构与设计3 智能车货匹配推荐算法的实现**1\. 问题陈述****2\. 算法模型**3\. 模型构建总览 **4 司机标签体系的搭建及算法****1\. 冷启动**2\. LSTM多标签模型算法 5 货运价格预测6 总结7 部分核心代码8 最后 0 前言 &#x1f5…...

WPF 附加属性+控件模板,完成自定义控件。建议观看HandyControl源码

文章目录 相关连接前言需要实现的效果附加属性添加附加属性&#xff0c;以Test修改FontSize为例依赖属性使用触发器使用直接操控 结论 控件模板&#xff0c;在HandyControl的基础上面进行修改参考HandyControl的源码控件模板原型控件模板 控件模板触发器完整样式简单使用 结论 …...

SpringBoot+Vue 毕业设计效率提升实战:从脚手架到自动化部署的全链路优化

SpringBootVue 毕业设计效率提升实战&#xff1a;从脚手架到自动化部署的全链路优化 毕业设计是每个计算机相关专业学生必须跨越的一道坎。回想我自己的经历&#xff0c;以及身边同学的故事&#xff0c;一个普遍的现象是&#xff1a;大家往往在技术选型和环境搭建上就耗费了大量…...

破局B站音频提取难题:BilibiliDown革新性解决方案全解析

破局B站音频提取难题&#xff1a;BilibiliDown革新性解决方案全解析 【免费下载链接】BilibiliDown (GUI-多平台支持) B站 哔哩哔哩 视频下载器。支持稍后再看、收藏夹、UP主视频批量下载|Bilibili Video Downloader &#x1f633; 项目地址: https://gitcode.com/gh_mirrors…...

纺织抗菌,选对材料才关键

在纺织行业中&#xff0c;抗菌消臭性能是提升产品附加值的核心抓手&#xff0c;其中贴身衣物、家纺等贴身类产品&#xff0c;因长期接触人体或所处环境特性&#xff0c;细菌滋生、异味残留等问题尤为突出。DN128抗菌消臭剂作为高效无机消臭材料&#xff0c;可广泛用作面料及家纺…...

FPGA时序约束实战:input delay约束的5个常见坑点及解决方法

FPGA时序约束实战&#xff1a;input delay约束的5个常见坑点及解决方法 在FPGA开发中&#xff0c;时序约束的正确设置往往是项目成败的关键。我曾在一个高速数据采集项目中&#xff0c;因为input delay约束设置不当&#xff0c;导致系统在高温环境下出现偶发性数据错误&#xf…...

从ChatGPT到机器翻译:GRPO算法如何优化大语言模型的生成效果?

GRPO算法&#xff1a;大语言模型生成效果优化的新范式 在自然语言处理领域&#xff0c;序列生成任务的质量优化一直是研究热点。从ChatGPT的对话流畅度到机器翻译的准确性&#xff0c;生成效果直接影响用户体验。传统优化方法如PPO虽然有效&#xff0c;但在处理复杂语言任务时存…...

CosyVoice3实战:3秒克隆老板声音,自动生成会议纪要语音

CosyVoice3实战&#xff1a;3秒克隆老板声音&#xff0c;自动生成会议纪要语音 1. 为什么你需要这个声音克隆神器&#xff1f; 想象一下这个场景&#xff1a;周一早上9点的例会刚结束&#xff0c;你需要立即整理会议录音并生成会议纪要。传统方法可能需要你&#xff1a; 花1…...

实测对比:openEuler三大桌面环境UKUI/DDE/XFCE安装体验与性能消耗

实测对比&#xff1a;openEuler三大桌面环境UKUI/DDE/XFCE安装体验与性能消耗 当技术决策者面对openEuler操作系统时&#xff0c;桌面环境的选择往往成为影响工作效率的关键因素。本文将基于openEuler 24.03 LTS环境&#xff0c;深度实测UKUI、DDE和XFCE三大主流桌面环境&…...

Crystals Kyber算法实战:5分钟搞定密钥封装机制(KEM)配置

Crystals Kyber算法实战&#xff1a;5分钟搞定密钥封装机制&#xff08;KEM&#xff09;配置 在当今数字安全领域&#xff0c;后量子密码学正从理论走向工程实践。作为NIST后量子密码标准化项目的优胜算法&#xff0c;Kyber以其高效的格基加密机制&#xff0c;正在重构密钥分发…...

像素幻梦快速上手指南:3步完成16-bit风格图像生成与内存流导出

像素幻梦快速上手指南&#xff1a;3步完成16-bit风格图像生成与内存流导出 1. 认识像素幻梦创意工坊 像素幻梦创意工坊&#xff08;Pixel Dream Workshop&#xff09;是一款基于FLUX.1-dev扩散模型构建的像素艺术生成工具。它采用明亮的16-bit像素风格界面设计&#xff0c;为…...

B2B企业获客技术瓶颈:矩阵跃动龙虾机器人+GEO,精准捕捉采购端搜索流量

在数字化转型深度渗透、AI技术全面重构行业逻辑的当下&#xff0c;B2B行业的获客模式已从传统粗放式的展会、电话陌拜&#xff0c;转向精细化、智能化、数据化的精准获客赛道。不同于C端流量的泛化传播&#xff0c;B2B采购决策链路长、决策人群集中&#xff08;采购负责人、技术…...