java8中为什么放弃了ConcurrentMap的分段锁,采取了什么新的策略

Java8中ConcurrentHashMap放弃分段锁策略,采用synchronized+CAS实现线程安全。此举旨在减少内存开销,提高GC效率,利用JVM优化如锁粗化、锁消除等。新方案针对链表首节点使用synchronized,确保线程安全同时,通过CAS尝试插入元素,提高并发性能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

java8中为什么放弃了ConcurrentMap的分段锁,采取了什么新的策略

前言

ConcurrentMap,它是一个接口,是一个能够支持并发访问的java.util.map集合;

ConcurrentHashMap是一个线程安全,并且是一个高效的HashMap。

spring 缓存注解 通过查看源代码发现将数据存在ConcurrentMap中

它的原理是引用了内部的 Segment ( ReentrantLock ) 分段锁,和hashmap一样,在jdk1.7中ConcurrentHashMap的底层数据结构是数组加链表。和hashmap不同的是ConcurrentHashMap中存放的数据是一段段的,即由多个Segment(段)组成的。每个Segment中都有着类似于数组加链表的结构。

保证在操作不同段 map 的时候, 可以并发执行, 操作同段 map 的时候,进行锁的竞争和等待。从而达到线程安全, 且效率大于 synchronized。

但是在 Java 8 之后, JDK 却弃用了这个策略,重新使用了 synchronized+cas。

弃用原因

通过 JDK 的源码和官方文档看来, 他们认为的弃用分段锁的原因由以下几点:

  1. 加入多个分段锁浪费内存空间。
  2. 生产环境中, map 在放入时竞争同一个锁的概率非常小,分段锁反而会造成更新等操作的长时间等待。
  3. 为了提高 GC 的效率

什么是可重入锁(递归锁)

定义:当某个线程已经获得某个锁,可以再次获取锁而不会发生死锁,可以多次获取相同的锁。
优点:可以再一定程度上避免死锁。

新的方案

既然弃用了分段锁, 那么一定由新的线程安全方案, 我们来看看源码是怎么解决线程安全的呢?(源码保留了segment 代码, 但并没有使用)

put
首先通过 hash 找到对应链表过后, 查看是否是第一个object, 如果是, 直接用cas原则插入,无需加锁。
然后, 如果不是链表第一个object, 则直接用链表第一个object加锁,这里加的锁是synchronized,虽然效率不如 ReentrantLock, 但节约了空间,这里会一直用第一个object为锁, 直到重新计算map大小, 比如扩容或者操作了第一个object为止。

为什么是synchronized,而不是ReentranLock

  1. 减少内存开销
    假设使用可重入锁来获得同步支持,那么每个节点都需要通过继承AQS来获得同步支持。但并不是每个节点都需要获得同步支持的,只有链表的头节点(红黑树的根节点)需要同步,这无疑带来了巨大内存浪费。
  2. 获得JVM的支持
    可重入锁毕竟是API这个级别的,后续的性能优化空间很小。
    synchronized则是JVM直接支持的,JVM能够在运行时作出相应的优化措施:锁粗化、锁消除、锁自旋等等。这就使得synchronized能够随着JDK版本的升级而不改动代码的前提下获得性能上的提升。
### JobThread中CompletionException和ConcurrentModificationException错误的原因及解决方案 #### 一、CompletionException 错误分析与解决 `CompletionException` 是 Java 中 `CompletableFuture` 或其他异步任务框架中的常见异常。它通常表示某个异步任务在执行过程中发生了未捕获的异常,而该异常会被封装到 `CompletionException` 中并向上抛出。 - **原因** 当 `JobThread` 使用 `CompletableFuture` 执行异步任务时,如果子任务内部发生异常(如空指针异常、非法参数异常等),这些异常会被封装成 `CompletionException` 并传递给调用方[^1]。 - **解决方案** 可通过以下方式处理: - 明确捕获异常:使用 `.exceptionally()` 方法显式定义如何处理异常情况。 ```java CompletableFuture<Void> future = CompletableFuture.runAsync(() -> { // 模拟可能引发异常的任务 throw new RuntimeException("Task failed"); }).exceptionally(ex -> { System.err.println("Caught exception: " + ex.getMessage()); return null; }); ``` - 调试日志记录:增加详细的日志信息以便定位问题源头。 ```java try { future.get(); } catch (InterruptedException | ExecutionException e) { Throwable cause = e.getCause(); if (cause instanceof CompletionException) { System.err.println("Root cause of CompletionException: " + cause.getCause().getMessage()); } } ``` --- #### 二、ConcurrentModificationException 错误分析与解决 `ConcurrentModificationException` 表示集合在迭代期间被外部线程或方法修改而导致的一致性破坏。这种异常常发生在多线程环境下对共享集合的操作。 - **原因** 如果 `JobThread` 的任务涉及对共享集合(如 `ArrayList`、`HashMap` 等)的并发访问,在某些场景下可能会触发此异常。例如,当一个线程正在遍历集合的同时,另一个线程对该集合进行了结构性修改(如添加或删除元素)。这违反了迭代器的安全假设[^2]。 - **解决方案** 下面列举了几种常见的解决策略: - **Collections.synchronizedXXX() 方法** 对于简单的线程安全需求,可以使用 `Collections.synchronizedList()` 或 `Collections.synchronizedSet()` 来包装原始集合。需要注意的是,虽然这种方法提供了基本的同步机制,但在复杂操作(如迭代)时仍需手动加锁。 ```java List<String> synchronizedList = Collections.synchronizedList(new ArrayList<>()); synchronized(synchronizedList) { Iterator<String> iterator = synchronizedList.iterator(); while (iterator.hasNext()) { String item = iterator.next(); // 处理逻辑... } } ``` - **CopyOnWriteArrayList / CopyOnWriteArraySet** 这些类适用于读多写少的场景。它们通过每次写入时创建新副本的方式来避免并发冲突,从而天然支持线程安全。 ```java Set<String> threadSafeSet = new CopyOnWriteArraySet<>(); threadSafeSet.add("item1"); threadSafeSet.remove("item1"); ``` - **ConcurrentHashMap** 在需要高性能并发哈希表的情况下,推荐使用 `ConcurrentHashMap` 替代普通的 `HashMap`。它的设计基于分段锁技术,能够显著提升性能。 ```java Map<String, Integer> concurrentMap = new ConcurrentHashMap<>(); concurrentMap.put("key", 1); int value = concurrentMap.computeIfAbsent("key", k -> 0); ``` --- ### 总结 对于 `CompletionException` 和 `ConcurrentModificationException`,分别采取针对性措施可有效解决问题。前者主要依赖于完善的异常捕捉机制;后者则可以通过选用合适的线程安全容器或者合理控制并发行为来规避风险。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值