ConcurrentHashMap让你的代码飞起来!【Java多线程必备】

发布时间:2023-05-04 10:27:11

一、介绍

它可以支持高并发的读写操作,并且具有良好的扩展性。它在那里 Java 1.5 并在中引入 Java 1.8 优化中间。ConcurrentHashMap 底层采用分段锁技术实现线程安全,其内部结构包括多个 Segment,每个 Segment 本质上是线程安全的哈希表。

二、特性

1. 线程安全

put、get、remove 所有其他操作都是线程安全的,不需要额外的同步机制,因此可以在多线程环境中安全使用。

2. 高效性

利用分段锁技术实现线程安全 Segment 都有锁,不同的线程可以同时访问不同的线程 Segment,提高并发性。

4. 支持高并发读写操作

同时支持多线程读取操作,不会阻塞写作操作,因此可以在高并发环境中保持良好的性能。

5. 支持更多操作

replace、putIfAbsent 等等,方便开发人员实现更多的业务需求。

6. ConcurrentHashMap 的性能优化

Java 8 一些优化。其中一个重要的优化是利用它 CAS 更换以前的操作 Synchronized 关键字,从而减少了锁的争用,提高了并发度。

三、原理

采用分段锁技术实现线程安全,其内部结构包括多个 Segment,每个 Segment 本质上是线程安全的哈希表。每个 Segment 都有锁,不同的线程可以同时访问不同的线程 Segment,提高并发性。在阅读操作中,多线程可以同时支持阅读操作,而在写作操作时,只需锁定相应的线程 Segment,对别人没有错 Segment 进行影响。

数组用于哈希表 + 链表 + 实现了红黑树的数据结构,其中数组用于存储哈希桶,链表和红黑树用于解决哈希冲突。当链表中的元素达到一定数量时,链表将转换为红黑树,以提高搜索和删除操作的性能。

四、使用场景

1. 高并发环境

适用于高并发环境,尤其是读写频繁的场景。在这种情况下,使用普通的Hashmap 可能会出现线程安全问题,使用Hashtable的效率相对较低。

2. 大规模数据存储

并发性能很好,支持动态调整 Segment 因此,大规模数据的存储和处理可以得到很好的支持。

3. 缓存

它非常适合作为缓存的存储结构,因为它支持高并发读写操作,并且具有良好的可扩展性。在缓存过程中,读写操作比写作操作频繁得多,可以提高缓存性能,同时确保线程安全。

五、注意事项

1. ConcurrentHashMap 迭代器一致性弱

数据可能存在一些不一致性问题。虽然 ConcurrentHashMap 无论是使用迭代器,都提供了各种各样的遍历,forEach 还是 Spliterator,需要注意的是,它们是弱一致性的,可能会看到过时的数据。

2. ConcurrentHashMap 的 putIfAbsent() 方法

putIfAbsent(K key, V value) 方法,可以是一个 key-value 对添加到 ConcurrentHashMap 但只有在这里 key 只有在不存在的情况下才能成功添加。该方法通常用于缓存中的添加操作,以避免重复添加相同的数据。

3. ConcurrentHashMap 的 size() 方法

它并不总是返回到准确的尺寸,因为在多线程环境中,可能会有一些数据不一致。虽然 ConcurrentHashMap 它提供了一些获取近似大小的方法,但需要注意的是,它们的精度可能会受到限制。

4. ConcurrentHashMap 初始容量

其初始容量和负载因素需要指定。为提高性能,建议将初始容量设置为预期存储。 key-value 大约是数量的两倍。若初始化容量设置过小,则可能导致扩容过程频繁,影响性能。

六、实际应用

1. 案例一

(1) 场景

一些使用ConcurrentHashmap的方法案例

(2) 代码

 

import java.util.concurrent.ConcurrentHashMap;/** * concurentHashMapcase * 一些简单使用ConcurentHashmap的方法 * * @author wxy * @since 2023-04-26 */public class concurentHashMapcase {    public static void main(String[] args) {        ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();        map.put("one", 1);        map.put("two", 2);        map.put("three", 3);        // 输出 1        System.out.println(map.get("one"));        map.putIfAbsent("four", 4);        map.putIfAbsent("four", 5);        map.putIfAbsent("four", 6);        // 输出 4        System.out.println(map.get("four"));        map.replace("two", 22);        // 输出 22        System.out.println(map.get("two"));        map.remove("three");        // 输出 null        System.out.println(map.get("three"));    }}

 

ConcurrentHashMap让你的代码飞起来!【Java多线程必备】_ConcurrentHashMap

在这个示例代码中,我们创建了一个 ConcurrentHashMap 三个对象被添加到其中 key-value 是的。然后我们使用它 get() 、putIfAbsent()、replace() 、remove() 演示方法。

2. 案例二

(1) 场景

ConcurrentHashmap和Hashmap在多线程条件下, 缓存数据的准确性。

(2) 代码

import java.util.HashMap;import java.util.Map;import java.util.concurrent.ConcurrentHashMap;/** * concurentHashMapcase: 与HashMap相比,ConcurrentHashMap有什么根本区别? * CopyOnWriteArrayList: 线程安全。 * CopyOnWriteArrayList: 线程安全。 * ArrayList: 线程不安全。 * ConcurrentHashmap和Hashmap在多线程条件下, 缓存数据的准确性。 * * @author wxy * @since 2023-04-26 */public class concurentHashMapcase {    public static void main(String[] args) throws InterruptedException {        Map<String, String> concurrentHashMap = new ConcurrentHashMap<>(500);        Map<String, String> hashMap = new HashMap<>(500);        // 创建两个线程并启动它们        Thread thread1 = new Thread(() -> {            for (int index1 = 0; index1 < 1000; index1++ {                concurrentHashMap.put(Integer.toString(index1), Integer.toBinaryString(index1);                hashMap.put(Integer.toString(index1), Integer.toBinaryString(index1);            }        });        Thread thread2 = new Thread(() -> {            for (int index2 = 1000; index2 < 2000; index2++ {                concurrentHashMap.put(Integer.toString(index2), Integer.toBinaryString(index2);                hashMap.put(Integer.toString(index2), Integer.toBinaryString(index2);            }        });        thread1.start();        thread2.start();        // 等待两个线程结束        thread1.join();        thread2.join();        System.out.println("ConcurrentHashMap size: " + concurrentHashMap.size());        System.out.println("HashMap size: " + hashMap.size());    }}

运行结果如下:

ConcurrentHashMap让你的代码飞起来!【Java多线程必备】_多线程必备_02

为什么HashMap在上述代码中没有相同的键值对,但部分键值被覆盖?事实上,即使没有相同的键值对,也可能发生这种情况。

这是因为Hashmap不是线程安全的,它的内部结构由一个数组和一个单向链表组成。在多线程环境下,如果两个线程同时使用put,可能会发生以下情况:

1. 线程1正在向数组位置i添加键值对,但链表上的其他元素尚未复制到新的链表中。

2. 线程2还需要在数组位置i处添加一个键值对,发现此时链表上只有旧元素,因此也将其复制到新的链表中。

3. 线程1完成了它的操作,将新的键值插入到新链表的头部。

4. 线程2完成了它的操作,将新的键值插入到新链表的头部。

由于Hashmap的put方法没有同步,在这种情况下会出现并发修改异常。其中一个键值将被覆盖,最终导致Hashmap尺寸低于预期值。如果使用ConcurentHashmap,上述情况将不会发生

上一篇 使用DUMP数据块与BBED查看BLOCK对比数据库修改时的SCN
下一篇 Java Collection与Map详解

文章素材均来源于网络,如有侵权,请联系管理员删除。

标签: Java教程Java基础Java编程技巧面试题Java面试题