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

JAVA并发编程工具

文章目录

    • 并发工具
    • ConcurrentHashMap
      • 使用注意事项
        • 复合操作非原子性问题
        • 弱一致性问题
        • 不支持 Null 键值
      • 使用示例
    • BlockingDeque
      • LinkedBlockingDeque
      • ArrayBlockingDeque
      • ConcurrentLinkedQueue
    • CopyOnWriteArrayList / CopyOnWriteArraySet

并发工具

Concurrent类型的容器

  • 内部很多操作使用cas优化,一般可以提供较高吞吐量
  • 弱一致性
    • 遍历时弱一致性,例如,当利用迭代器遍历时,如果容器发生修改,迭代器仍然可以继续进行遍历,这时内容时旧的
    • 求大小弱一致性,size操作未必时100%准确
    • 读取弱一致性

遍历时如果发生了修改,对于非安全容器来讲,使用fail-fast机制也就是让遍历立刻失败,抛出ConcurrentModificationException,不再继续遍历

ConcurrentHashMap

  • 为什么要重写hashCode()和equals()

1.重复存储相同对象
若两个对象逻辑相等(根据业务规则,如字段值相同),但未重写 hashCode(),其默认哈希值(基于内存地址)不同。哈希集合(如 HashSet)会将其视为不同对象,导致重复存储.

Set<Person> set = new HashSet<>();
set.add(new Person("Alice", 30));  // 对象1
set.add(new Person("Alice", 30));  // 对象2(逻辑相同)
System.out.println(set.size());    // 输出 2(预期应为 1)

2.无法正确检索数据

HashMap 中,若用未重写 hashCode() 的对象作为键:
存储时:根据键的哈希值决定存储位置(桶索引)。
检索时:新键的哈希值不同,无法定位到原桶,返回 null

Map<Person, String> map = new HashMap<>();
map.put(new Person("Bob", 25), "Engineer");
String job = map.get(new Person("Bob", 25));  // 返回 null

3.性能下降/对象哈希值可能集中分布

  • JavaObject.hashCode() 默认实现基于对象的内存地址生成哈希值,哈希值分布依赖内存地址的分配模式,而非对象内容
  • JVM 分配内存时,若连续创建对象 obj1obj2obj3,其地址可能为 0x10000x10080x1010,哈希值对应为 100010081010(假设简化转换),默认哈希值的低位可能高度相似(如上述哈希值的低4位均为 000),导致计算出的桶索引集中在少数位置
    示例
对象内存地址哈希值桶索引 (15 & hash)
objA0x100040960 (因 4096 & 15 = 0)
objB0x200081920 (因 8192 & 15 = 0)
objC0x3000122880 (因 12288 & 15 = 0)

结果:所有对象被分配到索引0的桶中,冲突率100%

  • 集中分布的后果:多个对象被分配到同一桶中,HashMap 退化为链表(或红黑树),查询效率从 O(1) 降至 O(n)O(log n)

使用注意事项

复合操作非原子性问题

问题描述
单个操作(如 getput)是线程安全的,但多个操作的组合(如“检查再更新”)不保证原子性。例如:

// 线程不安全的库存扣减
public boolean processOrder(String productId, int quantity) {Integer stock = map.get(productId);if (stock >= quantity) {map.put(productId, stock - quantity); // 可能覆盖其他线程的修改return true;}return false;
}

风险:多个线程同时读取相同库存值并各自扣减,导致超卖(如实际库存 10,线程 A 和 B 均读到 10 并扣减 3 和 2,最终库存变为 7 而非 5)。

解决方案

使用原子方法:computeIfPresent()compute() 确保检查与更新原子化

map.computeIfPresent(productId, (k, v) -> v >= quantity ? v - quantity : v);

或配合 synchronizedReentrantLock 手动加锁(牺牲部分性能)。

弱一致性问题

问题描述

  • 迭代器弱一致性:迭代过程中若其他线程修改数据,迭代器可能反映部分修改或完全不反映,但不会抛 ConcurrentModificationException
  • size()/isEmpty() 不精确:返回的是近似值(无锁统计可能遗漏部分更新)。

::: tip
风险
实时统计场景(如实时监控大盘)可能读到过期数据
:::

  • 需强一致性的场景改用 Collections.synchronizedMap()Hashtable(性能更低)。 或通过额外锁机制同步
不支持 Null 键值

问题描述
键或值为 null 时会抛 NullPointerException(而 HashMap 允许)。
原因
多线程下无法区分“键不存在”和“键对应值为 null”,可能引发歧义。
解决方案

  • 改用 Optional 包装值(如 map.put(key, Optional.ofNullable(value)))。
  • 或确保业务逻辑中无需 null

使用示例

  • 示例一
// 使用 putIfAbsent:无论 "a" 是否存在,都会先创建 LongAdder 对象
conMap.putIfAbsent("a", new LongAdder());  // 可能浪费资源// 使用 computeIfAbsent:仅当 "a" 不存在时才执行 new LongAdder()
LongAdder longAdder = counter.computeIfAbsent("key", k -> new LongAdder()); // 按需创建
longAdder.increment();  // +1

BlockingDeque

LinkedBlockingDeque

  • 用一把锁,同一时刻,最多只允许有一个线程(生产者或消费者,二选一)执行
  • 用两把锁,同一时刻,可以允许两个线程同时(一个生产者与一个消费者)执行
    • 消费者与消费者线程仍然串行
    • 生产者与生产者线程仍然串行
      线程安全分析
  • 当节点总数大于2时(包括dummy节点),putLock保证的是last节点的线程安全,takeLock保证的是
    head节点的线程安全。两把锁保证了入队和出队没有竞争
  • 当节点总数等于2时(即一个dummy节点,一个正常节点)这时候,仍然是两把锁锁两个对象,不会竞
  • 当节点总数等于1时(就一个dummy节点)这时take线程会被notEmpty条件阻塞,有竞争,会阻塞

ArrayBlockingDeque

  • Linked支持有界,Array强制有界
  • Linked实现是链表,Array实现是数组
  • Linked是懒情的,而Array需要提前初始化Node数组
  • Linked每次入队会生成新Node,而ArrayNode是提前创建好的
  • Linked两把锁,Array一把锁

ConcurrentLinkedQueue

  • 两把【锁】,同一时刻,可以充许两个线程同时(一个生产者与一个消费者)执行
  • dummy节点的引入让两把【锁】将来锁住的是不同对象,避免竞争
  • 只是这【锁】使用了cas来实现

CopyOnWriteArrayList / CopyOnWriteArraySet

  • 适合读多写少的场景,用空间换取线程安全

CopyOnlriteArraySetCopyOnWriteArrayList的马甲
底层实现采用了写入时贝的思想,增删改操作会将底层数组拷贝一份,更改操作在新数组上执行,这时不影响其它线程的并发读,读写分离。

  • get弱一致性
  • 迭代器弱一致性
CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>();list.add(1);list.add(2);list.add(3);Iterator<Integer>iter =list.iterator();new Thread(()-> {list.remove(0);System.out.println(list);}).start();Thread.sleep(1000);while (iter.hasNext()) {System.out.println(iter.next());} 

不要觉得弱一致性就不好

  • 数据库的MVCC都是弱一致性的表现(mysql的可重复读)
  • 并发高和一致性是矛盾的,需要权衡
http://www.xdnf.cn/news/16945.html

相关文章:

  • [echarts] 更新数据
  • ForCenNet:文档图矫正迎来新SOTA(2025)
  • Wisdom SSH开启高效管理服务器的大门
  • ceph 14.2.22 nautilus Balancer 数据平衡
  • RK3588实现wlan直连
  • 一致连续性背后的直觉是什么?
  • 【通用视觉框架】基于QT+Halcon开发的流程拖拽式通用视觉框架软件,全套源码,开箱即用
  • windows mamba-ssm环境配置指南
  • 洛谷P4479第K大斜率
  • c#保留小数点后几位 和 保留有效数字
  • 【智能体agent】入门之--4.1 autogen agentic rag
  • C++继承中虚函数调用时机问题及解决方案
  • CG--逻辑判断1
  • 译 | BBC Studios团队:贝叶斯合成控制方法SCM的应用案例
  • C++ --- stack和queue的使用以及简单实现
  • 第三章 网络安全基础(一)
  • PendingIntent相关流程解析
  • 京东零售在智能供应链领域的前沿探索与技术实践
  • 逻辑回归召回率优化方案
  • 《协作画布的深层架构:React与TypeScript构建多人实时绘图应用的核心逻辑》
  • 插件升级:Chat/Builder 合并,支持自定义 Agent、MCP、Rules
  • Spring Boot 2.1.18 集成 Elasticsearch 6.6.2 实战指南
  • 使用GPU和NPU视频生成的优劣对比
  • 修改DeepSeek翻译得不对的V语言字符串文本排序程序
  • (线段树)SP2916 GSS5 / nfls #2899 查询最大子段和 题解
  • 烽火HG680-KX-海思MV320芯片-2+8G-安卓9.0-强刷卡刷固件包
  • 一种新的分布式ID生成方案--ULID
  • 自学嵌入式 day40 51单片机
  • Web开发-PHP应用弱类型脆弱Hash加密Bool类型Array数组函数转换比较
  • sqli-labs:Less-17关卡详细解析