ConcurrentHashMap
是 Java 中一个线程安全且高效的哈希表实现,它位于 java.util.concurrent
包下,在多线程环境下可以替代 HashTable
和同步包装器 Collections.synchronizedMap
。以下将详细介绍 ConcurrentHashMap
的底层原理和常用使用方法。
在 Java 7 中,ConcurrentHashMap
采用分段锁(Segment)机制来实现线程安全。
ConcurrentHashMap
内部包含一个 Segment
数组,每个 Segment
类似于一个小的 HashMap
,它继承自 ReentrantLock
。每个 Segment
维护着一个 HashEntry
数组,HashEntry
是一个链表节点,用于存储键值对。Segment
可以被不同的线程同时访问,因此在多线程环境下,只要访问的是不同的 Segment
,就不会产生锁竞争,从而提高了并发性能。只有当多个线程访问同一个 Segment
时,才需要竞争该 Segment
上的锁。Java 8 对 ConcurrentHashMap
进行了重大改进,摒弃了分段锁机制,采用 CAS(Compare-And-Swap)和 synchronized
来保证并发安全。
ConcurrentHashMap
采用数组 + 链表 + 红黑树的结构。当链表长度超过一定阈值(默认为 8)且数组长度大于 64 时,链表会转换为红黑树,以提高查找效率。synchronized
:
synchronized
进行同步。synchronized
:当需要对某个节点进行修改时,会使用 synchronized
对该节点进行加锁,保证同一时刻只有一个线程可以对该节点进行操作。ConcurrentHashMap
import java.util.concurrent.ConcurrentHashMap;
public class ConcurrentHashMapExample {
public static void main(String[] args) {
// 初始化一个空的 ConcurrentHashMap
ConcurrentHashMap map = new ConcurrentHashMap<>();
}
}
import java.util.concurrent.ConcurrentHashMap;
public class ConcurrentHashMapExample {
public static void main(String[] args) {
ConcurrentHashMap map = new ConcurrentHashMap<>();
// 添加单个元素
map.put("apple", 1);
// 如果指定的键不存在,则添加该键值对
map.putIfAbsent("banana", 2);
}
}
import java.util.concurrent.ConcurrentHashMap;
public class ConcurrentHashMapExample {
public static void main(String[] args) {
ConcurrentHashMap map = new ConcurrentHashMap<>();
map.put("apple", 1);
// 根据键获取值
Integer value = map.get("apple");
System.out.println(value); // 输出: 1
}
}
import java.util.concurrent.ConcurrentHashMap;
public class ConcurrentHashMapExample {
public static void main(String[] args) {
ConcurrentHashMap map = new ConcurrentHashMap<>();
map.put("apple", 1);
// 根据键删除元素
Integer removedValue = map.remove("apple");
System.out.println(removedValue); // 输出: 1
}
}
import java.util.concurrent.ConcurrentHashMap;
import java.util.Map;
public class ConcurrentHashMapExample {
public static void main(String[] args) {
ConcurrentHashMap map = new ConcurrentHashMap<>();
map.put("apple", 1);
map.put("banana", 2);
// 使用 for-each 循环遍历键值对
for (Map.Entry entry : map.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
}
}
import java.util.concurrent.ConcurrentHashMap;
public class ConcurrentHashMapExample {
public static void main(String[] args) {
ConcurrentHashMap map = new ConcurrentHashMap<>();
map.put("apple", 1);
map.put("banana", 2);
// 获取元素数量
int size = map.size();
System.out.println(size); // 输出: 2
}
}
在 Java 8 中,ConcurrentHashMap
相较于 Java 7 版本有了重大的优化,下面从数据结构、锁机制、并发操作等方面详细介绍这些优化点。
Segment
)机制,ConcurrentHashMap
内部是一个 Segment
数组,每个 Segment
又包含一个 HashEntry
数组,本质上相当于多个小的 HashMap
。synchronized
替代分段锁
Segment
可以被不同的线程同时访问,只有访问同一个 Segment
时才会产生锁竞争。但这种方式在某些场景下可能会导致锁的粒度较大,影响并发性能。synchronized
来保证并发安全。
synchronized
进行同步。synchronized
:当需要对某个节点进行修改时,会使用 synchronized
对该节点进行加锁,保证同一时刻只有一个线程可以对该节点进行操作。synchronized
在 Java 8 中进行了大量优化,性能有了显著提升,而且锁的粒度更小,只对单个节点加锁,相比 Java 7 的分段锁,并发性能得到了进一步提高。compute
、computeIfAbsent
和 computeIfPresent
等方法
compute
方法:允许根据键的当前值(如果存在)和给定的计算函数来计算并更新值。这个方法在多线程环境下可以安全地执行更新操作,避免了先检查后更新带来的并发问题。computeIfAbsent
方法:如果指定的键不存在,则使用给定的函数计算一个值,并将其插入到 ConcurrentHashMap
中。这个方法可以避免在多线程环境下重复计算相同键的值。computeIfPresent
方法:如果指定的键存在,则使用给定的函数计算一个新值,并更新该键对应的值。ConcurrentHashMap
提供了弱一致性的迭代器,在迭代过程中,即使其他线程对 ConcurrentHashMap
进行了修改,迭代器也能继续工作,不会抛出 ConcurrentModificationException
。这是因为迭代器在创建时会记录当前 ConcurrentHashMap
的快照,在迭代过程中会尽量反映出创建时的状态,同时也会尝试反映后续的修改,但不保证完全一致。这种弱一致性的设计在多线程环境下提供了更好的并发性能。以下是一个使用 computeIfAbsent
方法的示例代码:
java
import java.util.concurrent.ConcurrentHashMap;
public class ConcurrentHashMapComputeIfAbsentExample {
public static void main(String[] args) {
ConcurrentHashMap map = new ConcurrentHashMap<>();
// 如果键 "apple" 不存在,则计算值并插入
Integer value = map.computeIfAbsent("apple", k -> 1);
System.out.println(value); // 输出: 1
}
}
综上所述,Java 8 的 ConcurrentHashMap
通过数据结构、锁机制、并发操作和遍历操作等方面的优化,提高了并发性能和使用的便利性。