简介
ConcurrentHashMap 是 Java 中并发编程中常用的线程安全的哈希表(HashMap)实现。它具有以下几个显著的特点和优点,适合在特定的并发场景中使用:
-
线程安全性:
- ConcurrentHashMap 提供了并发访问的线程安全保证,可以安全地在多线程环境中进行读取和写入操作,而无需显式地使用外部同步机制(如 synchronized)。
-
分段锁设计:--- jdk 1.7
- ConcurrentHashMap 使用了分段锁(Segmented Locking)的技术,内部分为多个段(Segment),每个段都类似于一个小的 HashMap,各自维护一部分数据。
- 默认情况下,ConcurrentHashMap 中有 16 个段,因此支持最多 16 个线程同时进行写操作,这提高了并发性能。
-
高并发性能:
- 分段锁设计使得 ConcurrentHashMap 在读操作上可以实现真正的并发访问,即使在写操作也只会锁住对应的段而不是整个表,因此不同段之间的操作可以并行执行,提高了并发性能。
-
扩展性和灵活性:
- ConcurrentHashMap 在初始化时可以指定段的数量,可以根据实际需求调整以平衡并发性能和内存消耗。
- 它支持高并发的写入操作,同时保持良好的读取性能,适合于大部分读多写少的并发场景。
-
迭代安全性:
- ConcurrentHashMap 在进行迭代时,不会抛出 ConcurrentModificationException 异常,因为它通过一种复杂的方式确保迭代器能够安全地遍历表中的元素,即使在并发修改的情况下也能保证一定的可靠性。
应用场景
ConcurrentHashMap 适用于需要高并发访问且需要线程安全的场景,特别是在以下情况下可以考虑使用它:
-
缓存:作为缓存的数据结构,支持并发的读写操作,适用于需要快速访问和更新的缓存系统。
-
高并发计数器:用于统计访问次数或计数器的场景,例如网站访问统计、计数等。
-
分布式任务分发:在任务分发时,可以使用 ConcurrentHashMap 来存储任务状态或结果,多个线程可以并发地更新任务状态。
-
实时数据处理:对于需要实时更新和处理的数据结构,如实时日志处理系统,ConcurrentHashMap 可以支持高并发的写入和查询操作。
总之,ConcurrentHashMap 是一个强大的并发工具,通过其优化的设计和分段锁机制,能够在高并发环境下提供良好的性能和线程安全保证,是并发编程中常用的选择之一。
ConcurrentHashMap是如何保证线程安全的?
ConcurrentHashMap 在 Java 7 中采用了一种称为分段锁(Segmented Locking)的机制来保证线程安全性。具体来说,它的线程安全性是通过以下几个关键点来实现的:
-
分段锁机制:--- jdk 1.7
- ConcurrentHashMap 内部维护了一个由多个段(Segment)组成的数组,每个段其实就是一个小的 HashTable。
- 默认情况下,ConcurrentHashMap 中有 16 个段,理论上可以支持同时进行 16 个写操作(插入或更新)。
-
段内操作的加锁:
- 每个段都是一个独立的 HashTable,它们之间是相互独立的,因此在进行操作时,只需要锁定需要操作的段,而不影响其他段的操作。
- 这样做的好处是,在多线程的情况下,不同段的操作可以并行进行,提高了并发性能。
-
保证可见性:
- ConcurrentHashMap 使用了 volatile 变量来保证段数组的可见性,确保一个线程对段数组所做的修改对其他线程是可见的。
- 这样可以避免由于缓存一致性导致的问题。
-
写操作的安全性:
- 在写操作(插入或更新)时,需要锁定对应的段,但这个锁定是针对段而不是整个 ConcurrentHashMap,这样做提高了并发度。
- 这种方式下,不同段之间的写操作可以并行执行,只有在同一个段上的写操作才会被阻塞。
-
读操作的安全性:
- 读操作通常不需要加锁,因为段内的 HashTable 是线程安全的,读取操作是非阻塞的。
总结来说,ConcurrentHashMap 通过分段锁的方式,在保证整体数据一致性的同时,提高了并发度,避免了传统的全局锁对并发性能的影响。这种设计使得在多线程环境中,ConcurrentHashMap 可以提供较高的并发访问性能。
Java 8 ConcurrentHashMap 线程安全分析
CAS自旋(sun.misc.Unsafe U # compareAndSwapObject) + synchronized
源码分析 put(key,value)
如下判断顺序,保证先后关系:
1、如果map中的核心数组为空,那么当前线程尝试进行cas初始化数组,然后再次尝试put;
2、如果数组不为空,并且map中【数组at位置-哈希槽】为空,代表这个哈希槽没有node,那么尝试cas设置当前node到这个位置上;
3、如果数组中【数组at位置-哈希槽】不为空,并且当前位置node节点正在【转移-扩容中】,那么当前线程帮助旧的node进行转移;
4、如果数组在【数组at位置-哈希槽】不为空,并且位置node没有发生转移,则对哈希槽Node进行加锁,锁定位置,然后添加新的Node到链表或者红黑树中;
如下图: