java面试题-ConcurrentHashMap了解吗?说说实现原理
深度解析 Java ConcurrentHashMap
1. 引言
在Java中,HashMap
是一种高效但非线程安全的哈希表实现,而HashTable
是线程安全但效率较低的实现。为了兼顾线程安全和高效性,Java提供了ConcurrentHashMap
,它采用了分段锁技术。本文将深入探讨ConcurrentHashMap
的实现原理,从JDK 1.7到JDK 1.8的演进,并提供详细的代码示例,旨在帮助初学者更好地理解和使用这一关键的并发集合类。
2. JDK 1.7 实现原理
2.1 数据结构
ConcurrentHashMap
在JDK 1.7中通过数组 + 链表的方式实现。它由Segment
数组和Segment
元素里对应多个HashEntry
组成。每个Segment
继承自ReentrantLock
,实现了分段锁。
class Segment extends ReentrantLock {
// ...
}
class HashEntry {
volatile Object key;
volatile Object value;
HashEntry next;
// ...
}
class ConcurrentHashMap {
Segment[] segments;
// ...
}
2.2 分段锁
ConcurrentHashMap
采用了分段锁技术,即通过Segment
数组实现。每个线程在访问ConcurrentHashMap
时只需要占用其中一个Segment
的锁,不影响其他Segment
。
class ConcurrentHashMap {
// ...
void put(Object key, Object value) {
int hash = hash(key);
int segmentIndex = hash % segments.length;
Segment segment = segments[segmentIndex];
segment.lock();
try {
// ... put logic
} finally {
segment.unlock();
}
}
// ...
}
2.3 Put 方法
put
方法的逻辑较为复杂。它尝试加锁,如果失败则通过自旋或阻塞方式获取锁。然后通过hashcode
定位到HashEntry
,遍历链表,判断是否需要扩容,最后释放锁。
void put(Object key, Object value) {
// ...
segment.lock();
try {
// ... lock logic
// Locate HashEntry
HashEntry entry = findEntry(key, hash, segment);
// ... put logic
} finally {
segment.unlock();
}
}
2.4 Get 方法
get
方法相对简单,通过hash
定位到具体的Segment
,再通过一次hash
定位到具体的元素。由于HashEntry
中的value
属性是用volatile
修饰的,保证了其内存可见性。
Object get(Object key) {
int hash = hash(key);
int segmentIndex = hash % segments.length;
Segment segment = segments[segmentIndex];
// ... lock logic
HashEntry entry = findEntry(key, hash, segment);
// ... get logic
return entry.value;
}
3. JDK 1.8 变化
3.1 数据结构
在JDK 1.8中,ConcurrentHashMap
抛弃了原有的Segment
分段锁,采用了CAS
+ synchronized
来保证并发安全性。同时,HashEntry
改为Node
。
class Node {
volatile Object key;
volatile Object value;
Node next;
// ...
}
class ConcurrentHashMap {
Node[] table;
// ...
}
3.2 Put 方法
put
方法的逻辑也有所变化,根据key
计算出hash
值,判断是否需要进行初始化,利用CAS
尝试写入,如果失败则自旋。如果当前位置的hashcode
等于MOVED
,则需要扩容。如果都不满足,则利用synchronized
锁写入数据。
void put(Object key, Object value) {
int hash = hash(key);
// ... init logic
Node node = table[index];
if (node == null) {
// ... CAS logic
} else {
// ... synchronized logic
}
// ... resize logic
}
3.3 Get 方法
get
方法的逻辑相对保持不变,根据计算出来的hash
值寻址,如果在桶上直接返回值。如果是红黑树,按照树的方式获取值。如果是链表,按链表的方式遍历获取值。
Object get(Object key) {
int hash = hash(key);
// ... get logic
return node.value;
}
3.4 红黑树优化
JDK 1.8中最大的改动之一是当链表上的Node
数量超过8个时,将链表转换为红黑树,提高查询复杂度。
4. 完整代码示例
4.1 JDK 1.7 示例
4.1.1 初始化并插入数据
import java.util.concurrent.ConcurrentHashMap;
public class ConcurrentHashMapExample {
public static void main(String[] args) {
// 初始化 ConcurrentHashMap
ConcurrentHashMap<String, Integer> concurrentHashMap = new ConcurrentHashMap<>();
// 插入数据
concurrentHashMap.put("One", 1);
concurrentHashMap.put("Two", 2);
concurrentHashMap.put("Three", 3);
System.out.println("JDK 1.7 ConcurrentHashMap: " + concurrentHashMap);
}
}
import java.util.concurrent.ConcurrentHashMap;
public class ConcurrentHashMapExample {
public static void main(String[] args) {
// 初始化 ConcurrentHashMap
ConcurrentHashMap<String, Integer> concurrentHashMap = new ConcurrentHashMap<>();
// 插入数据
concurrentHashMap.put("One", 1);
concurrentHashMap.put("Two", 2);
concurrentHashMap.put("Three", 3);
System.out.println("JDK 1.7 ConcurrentHashMap: " + concurrentHashMap);
}
}
4.1.2 多线程并发操作
import java.util.concurrent.ConcurrentHashMap;
public class ConcurrentHashMapMultiThreadExample {
public static void main(String[] args) {
// 初始化 ConcurrentHashMap
ConcurrentHashMap<String, Integer> concurrentHashMap = new ConcurrentHashMap<>();
// 多线程并发操作
Runnable runnable = () -> {
for (int i = 0; i < 5; i++) {
concurrentHashMap.put("Key" + i, i);
}
};
// 创建多个线程
Thread thread1 = new Thread(runnable);
Thread thread2 = new Thread(runnable);
// 启动线程
thread1.start();
thread2.start();
}
}
import java.util.concurrent.ConcurrentHashMap;
public class ConcurrentHashMapMultiThreadExample {
public static void main(String[] args) {
// 初始化 ConcurrentHashMap
ConcurrentHashMap<String, Integer> concurrentHashMap = new ConcurrentHashMap<>();
// 多线程并发操作
Runnable runnable = () -> {
for (int i = 0; i < 5; i++) {
concurrentHashMap.put("Key" + i, i);
}
};
// 创建多个线程
Thread thread1 = new Thread(runnable);
Thread thread2 = new Thread(runnable);
// 启动线程
thread1.start();
thread2.start();
}
}
4.2 JDK 1.8 示例
import java.util.concurrent.ConcurrentHashMap;
public class ConcurrentHashMapExample {
public static void main(String[] args) {
// 初始化 ConcurrentHashMap
ConcurrentHashMap<String, Integer> concurrentHashMap = new ConcurrentHashMap<>();
// 插入数据
concurrentHashMap.put("One", 1);
concurrentHashMap.put("Two", 2);
concurrentHashMap.put("Three", 3);
System.out.println("JDK 1.8 ConcurrentHashMap: " + concurrentHashMap);
}
}
import java.util.concurrent.ConcurrentHashMap;
public class ConcurrentHashMapExample {
public static void main(String[] args) {
// 初始化 ConcurrentHashMap
ConcurrentHashMap<String, Integer> concurrentHashMap = new ConcurrentHashMap<>();
// 插入数据
concurrentHashMap.put("One", 1);
concurrentHashMap.put("Two", 2);
concurrentHashMap.put("Three", 3);
System.out.println("JDK 1.8 ConcurrentHashMap: " + concurrentHashMap);
}
}
备注
关注站长获取更多详情:
- 本文标签: Java 面试题
- 本文链接: https://www.jietongc.com/article/128
- 版权声明: 本文由大熊科技原创发布,转载请遵循《署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)》许可协议授权