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

【避坑指南】初始化与更新共享数据赋值的一致性问题

文章目录

  • 一、原始代码分析
    • 具体问题:
    • 优化方案
  • 二、优化后代码
    • 优化点说明:
  • 三、优秀开源框架方案使用参考

在并发系统中,共享数据的初始化与更新若缺乏一致性控制,极易因线程竞争或可见性缺失导致脏读、状态不一致、引发线程安全漏洞等问题。本文通过一个配置热更新的代码案例,剖析下问题的原因和优化方案,并展示如何通过final关键字固化初始引用,结合CopyOnWriteArrayList的写时复制机制,采用线程安全容器与不可变对象重构前后的核心代码对比,解决资源竞争和可见性问题,并提升性能。

一、原始代码分析

原始代码中,在 DynamicTablesConfiguration 类中,dynamicTablesConfigList 是一个共享的数据结构,用于存储动态表的配置信息。
然而,dynamicTablesConfigList初始化和更新过程中存在一些潜在的问题,可能导致数据不一致或线程安全问题。

@Configuration
@NacosCustomNotificationConfigurationEnabled
@AutoConfigureBefore(value = { NacosCustomNotificationConfig.class })
@Data
@Slf4j
public class DynamicTablesConfiguration {@Beanpublic DynamicTablesConfigurationNotificationListener newDynamicTablesConfigurationListener() {return new DynamicTablesConfigurationNotificationListener(this);}public void changeDynamicTablesConfig(List<DynamicTableDTO> dynamicTablesConfigs) {if (TypeChecker.isEmpty(dynamicTablesConfigs)) {return;}dynamicTablesConfigs.forEach(x -> {String schema = x.getSchema();if (TypeChecker.isEmpty(myDynamicSchemas) || myDynamicSchemas.contains(schema)) {if (TypeChecker.isEmpty(x.getDyTableNameList())) {return;}x.getDyTableNameList().forEach(t -> dynamicTablesConfigList.add(schema + StringPool.DOT + t));}});log.info(">>>>>>>DynamicTablesConfiguration changed config: {}", dynamicTablesConfigList);}@Value("${point.dynamic.schemas:}")private List<String> myDynamicSchemas;private List<String> dynamicTablesConfigList = Collections.synchronizedList(Lists.newArrayList());}

具体问题:

  • 初始化问题:
    dynamicTablesConfigList 使用了 Collections.synchronizedList 包装,确保线程安全。
    但是,dynamicTablesConfigList 的初始化是在类实例化时进行的,如果在多线程环境下,可能会导致多个实例同时初始化该列表,从而引发潜在的问题。
  • 更新问题:
    changeDynamicTablesConfig 方法中,对 dynamicTablesConfigList 进行了添加操作,但由于 dynamicTablesConfigList 是一个共享的可变对象,如果多个线程同时调用该方法,可能会导致数据不一致或丢失更新。

优化方案

为了确保初始化和更新共享数据的一致性和线程安全性,可以采取以下优化措施:

  • 使用不可变对象:
    将 dynamicTablesConfigList 设计为不可变对象,每次更新时生成一个新的列表实例,而不是直接修改现有列表。这可以避免并发修改带来的问题。
  • 使用原子引用:
    使用 AtomicReference 包装 dynamicTablesConfigList,确保在更新时的原子性操作。
  • 双重检查锁定:
    在初始化时使用双重检查锁定(Double-Checked Locking)模式,确保在多线程环境下只初始化一次。
  • 使用读写锁:
    使用 ReadWriteLock 来管理对 dynamicTablesConfigList 的读写操作,提高并发性能。

二、优化后代码

以下是一个优化后的示例,使用 CopyOnWriteArrayList 和不可变列表来确保数据的一致性和线程安全性:

@Configuration
@NacosCustomNotificationConfigurationEnabled
@AutoConfigureBefore(value = {NacosCustomNotificationConfig.class})
@Data
@Slf4j
public class DynamicTablesConfiguration {@Beanpublic DynamicTablesConfigurationNotificationListener newDynamicTablesConfigurationListener() {return new DynamicTablesConfigurationNotificationListener(this);}public void changeDynamicTablesConfig(List<DynamicTableDTO> dynamicTablesConfigs) {if (TypeChecker.isEmpty(dynamicTablesConfigs)) {return;}// 使用临时列表来收集新的配置List<String> newConfigs = new CopyOnWriteArrayList<>();dynamicTablesConfigs.forEach(x -> {String schema = x.getSchema();if (TypeChecker.isEmpty(myDynamicSchemas) || myDynamicSchemas.contains(schema)) {if (TypeChecker.isEmpty(x.getDyTableNameList())) {return;}x.getDyTableNameList().forEach(t -> newConfigs.add(schema + StringPool.DOT + t));}});// 替换旧的配置列表dynamicTablesConfigList.clear();dynamicTablesConfigList.addAll(newConfigs);log.info(">>>>>>>DynamicTablesConfiguration changed config: {}", dynamicTablesConfigList);}@Value("${point.dynamic.schemas:}")private List<String> myDynamicSchemas;private final CopyOnWriteArrayList<String> dynamicTablesConfigList = new CopyOnWriteArrayList<>();public List<String> getDynamicTablesConfigList() {return new CopyOnWriteArrayList<>(dynamicTablesConfigList);}
}

优化点说明:

  • 使用 final 关键字:确保 dynamicTablesConfigList 在初始化后不能被重新赋值。

  • 使用 CopyOnWriteArrayList:确保对列表的读写操作是线程安全的。

  • 方法职责清晰,changeDynamicTablesConfig 负责更新配置,getDynamicTablesConfigList 提供只读访问。

  • 获取共享数据的方法
    getDynamicTablesConfigList() 返回副本以保护原始数据

三、优秀开源框架方案使用参考

以下是一些优秀开源框架中使用 final 关键字和 CopyOnWriteArrayList 来处理初始化与更新共享数据赋值一致性的方案。这些框架通过线程安全的集合以及不可变引用确保了多线程环境下的数据一致性。

比如:Spring Framework - 使用 CopyOnWriteArrayList 管理监听器
Spring 框架在事件监听器注册机制中广泛使用了 CopyOnWriteArrayList 来存储动态监听器列表,同时通过 final 修饰符确保引用不可变。
示例代码:

import org.springframework.context.event.EventListener;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;public class SpringEventListenerManager {private final CopyOnWriteArrayList<EventListener> listeners = new CopyOnWriteArrayList<>();public void registerListener(EventListener listener) {listeners.add(listener);}public void unregisterListener(EventListener listener) {listeners.remove(listener);}public void notifyListeners(Event event) {for (EventListener listener : listeners) {listener.onEvent(event);}}public interface EventListener {void onEvent(Event event);}public static class Event {private String message;public Event(String message) {this.message = message;}public String getMessage() {return message;}}
}

特点
使用 final 修饰 CopyOnWriteArrayList,确保其引用不可变。
利用 CopyOnWriteArrayList 的特性,在多线程环境下安全地管理监听器列表。

以上开源框架的源码中,分别使用了该技术点来处理初始化与更新共享数据赋值的一致性问题。

http://www.xdnf.cn/news/18269.html

相关文章:

  • 【数模国奖冲刺】备赛过程中的常见问题
  • Linux 服务:RAID 级别解析与 mdadm 工具实操指南
  • SWMM排水管网水力、水质建模及在海绵与水环境中的应用技术-模拟降雨和污染物质经过地面、排水管网、蓄水和处理
  • 计算机大数据毕业设计推荐:基于Hadoop+Spark的食物口味差异分析可视化系统【源码+文档+调试】
  • 第一阶段C#基础-13:索引器,接口,泛型
  • 【网络安全实验报告】实验六: 病毒防护实验
  • 【PZ-ZU47DR-KFB】璞致FPGA ZYNQ UltraScalePlus RFSOC QSPI Flash 固化常见问题说明
  • 【P38 6】OpenCV Python——图片的运算(算术运算、逻辑运算)加法add、subtract减法、乘法multiply、除法divide
  • 如何在服务器 clone github 项目
  • 【Linux开发】错误更改bash.sh导致PATH环境变量被破坏所有命令不可用的解决方法
  • 【菜狗学聚类】时序数据聚类算法和相关论文
  • 算法-每日一题(DAY13)两数之和
  • Centos7使用lamp架构部署wordpress
  • CentOS 7 LAMP快速部署WordPress指南
  • 20. 云计算-Service MeshServerless
  • 时序数据库 Apache IoTDB:从边缘到云端Apache IoTDB 全链路数据管理能力、部署流程与安全特性解读
  • 基于51单片机WIFI心率计脉搏体温测量仪APP设计
  • 加密资产投资的六种策略:稳定币合规后的 Web3 投资和 RWA
  • RabbitMQ ,消息进入死信交换机
  • React diff Vue diff介绍
  • 嵌入式学习硬件I.MX6ULL(五)按键 中断 GIC OCP原则
  • 云原生:重塑软件世界的技术浪潮与编程语言选择
  • 【每天学点‘音视频’】前向纠错 和 漏包重传
  • Flask 入门详解:从零开始构建 Web 应用
  • Linux中基于Centos7使用lamp架构搭建个人论坛(wordpress)
  • Dify web前端源码本地部署详细教程
  • 软件测试覆盖率:真相与实践
  • 【论文阅读69】-DeepHGNN复杂分层结构下的预测
  • Mybatis执行sql流程(一)
  • Dijkstra和多层图 0