原创

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); }}

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(); }}

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); }}


备注

关注站长获取更多详情:

file

正文到此结束
本文目录