Wrayの知识库 Wrayの知识库
首页
  • Java 基础
  • Java 集合
  • Java 并发
  • Java IO
  • JVM
  • Spring Framework
  • Spring Boot
  • Spring Cloud
  • Spring Security
  • MySQL
  • Redis
  • MacOS
  • Linux
  • Windows
  • 纸质书
  • 电子书
  • 学习课程
疑难杂症
GitHub (opens new window)
首页
  • Java 基础
  • Java 集合
  • Java 并发
  • Java IO
  • JVM
  • Spring Framework
  • Spring Boot
  • Spring Cloud
  • Spring Security
  • MySQL
  • Redis
  • MacOS
  • Linux
  • Windows
  • 纸质书
  • 电子书
  • 学习课程
疑难杂症
GitHub (opens new window)
  • Java基础

    • Java概述
    • Java语法
    • 面向对象编程
    • Java数组
    • String字符串
    • 异常处理
  • Java集合

    • Java集合概述
    • ArrayList
    • LinkedList
    • HashMap
    • LinkedHashMap
    • HashSet
    • TreeMap
    • Queue&Deque
  • Java并发

    • Java并发概述
    • 线程与进程
    • Thread类与线程生命周期
    • 线程安全
    • synchronized关键字
    • volatile关键字
    • Java内存模型(JMM)
    • 线程间通信
    • 线程池
    • 并发工具类
    • 原子操作类Atomic
    • 并发锁
    • 并发容器
    • ConcurrentHashMap
      • 1. ConcurrentHashMap 的基本概念
      • 2. ConcurrentHashMap 的实现原理
      • 3. ConcurrentHashMap 的重要特性
        • 3.1 高效并发读写
        • 3.2 红黑树优化
      • 4. ConcurrentHashMap 的源码分析
        • 4.1 put 方法
        • 4.2 get 方法
      • 5. ConcurrentHashMap 的应用场景
      • 6. ConcurrentHashMap 与其他 Map 的比较
      • 7. 总结
    • BlockingQueue
    • CopyOnWriteArrayList
    • ThreadLocal
    • Fork/Join框架
    • ScheduledThreadPoolExecutor
    • CompletableFuture
  • Java IO

    • Java IO概述
  • JVM

    • JVM概述
  • Java
  • Java并发
Wray
2024-10-31
目录

ConcurrentHashMap

ConcurrentHashMap 是 Java 中一种线程安全的哈希表实现,用于在高并发场景下替代 HashMap 的使用。它通过细粒度的锁和无锁的读操作,提供了高效的并发读写性能,是 Java 并发编程中的重要工具之一。

# 1. ConcurrentHashMap 的基本概念

  • 线程安全性:ConcurrentHashMap 通过细粒度的锁和 CAS(Compare-And-Swap)操作来实现线程安全,确保多个线程可以并发地进行读写操作而不会导致数据不一致。
  • 无锁读取:ConcurrentHashMap 的读取操作大多数情况下是无锁的,依赖于 volatile 变量的可见性保证,以及 CAS 保证修改的原子性,因此读取操作非常高效。
  • 高并发性:通过分段锁和更细粒度的锁机制,ConcurrentHashMap 实现了并发读取和写入,在高并发场景中提供了比 Hashtable 更好的性能。

# 2. ConcurrentHashMap 的实现原理

  • JDK 1.7 的分段锁机制:在 JDK 1.7 中,ConcurrentHashMap 使用了分段锁(Segment)机制。每个 Segment 相当于一个独立的小哈希表,不同的线程可以同时操作不同的 Segment,从而提高并发性能。每个 Segment 使用 ReentrantLock 实现锁定,确保线程安全。
  • JDK 1.8 的改进:在 JDK 1.8 中,ConcurrentHashMap 摒弃了 Segment 的设计,转而采用了一种更细粒度的锁和 CAS 操作。
    • 数据结构:使用数组 + 链表 + 红黑树的方式存储数据。当桶(bucket)中的链表长度超过一定阈值(默认 8)时,会将链表转换为红黑树,以提高查询效率。
    • 锁的粒度:使用 synchronized 锁住特定的桶节点,而不是锁住整个数组,从而实现更高的并发性。

# 3. ConcurrentHashMap 的重要特性

# 3.1 高效并发读写

  • 读取操作:大多数读取操作是无锁的,依赖于 volatile 变量和 CAS 操作来保证可见性和原子性。因此,读取操作非常高效,特别是在多线程环境下。
  • 写入操作:写入操作使用 CAS 和 synchronized,当 CAS 操作失败时,才会退化到使用 synchronized 来锁住相应的桶(bucket)。这种方式确保了写操作的线程安全,同时尽可能减少锁的持有时间,提高了并发性。

# 3.2 红黑树优化

  • 链表与红黑树:与 HashMap 类似,当桶中的链表长度超过阈值(默认 8)时,链表会转换为红黑树,以减少查询时间复杂度,提升查找效率。红黑树的查找复杂度为 O(log n),相比链表的 O(n) 更加高效。

# 4. ConcurrentHashMap 的源码分析

# 4.1 put 方法

  • put(K key, V value) 方法:put 方法用于将键值对插入到 ConcurrentHashMap 中。它首先计算键的哈希值,然后定位到对应的桶。如果桶为空,则通过 CAS 操作直接插入新节点;如果桶不为空,则使用 synchronized 锁住桶节点,确保线程安全。
    public V put(K key, V value) {
        return putVal(key, value, false);
    }
    
    private final V putVal(K key, V value, boolean onlyIfAbsent) {
        // 省略部分代码
        int hash = spread(key.hashCode());
        Node<K,V>[] tab; Node<K,V> f; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
            if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null)))
                break;                   // No lock when adding to empty bucket
        }
        else {
            synchronized (f) {
                // 锁住桶节点,确保线程安全
                // 省略具体实现
            }
        }
        // 省略部分代码
    }
    
    • CAS 操作:在桶为空的情况下,使用 CAS 操作直接插入新节点,避免加锁。
    • synchronized 锁:当桶中已有节点时,使用 synchronized 锁住桶节点,确保写操作的线程安全。

# 4.2 get 方法

  • get(Object key) 方法:get 方法用于获取指定键的值。首先计算键的哈希值,然后定位到对应的桶,通过遍历链表或红黑树来查找相应的值。
    public V get(Object key) {
        Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
        int h = spread(key.hashCode());
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (e = tabAt(tab, (n - 1) & h)) != null) {
            if ((eh = e.hash) == h) {
                if ((ek = e.key) == key || (ek != null && key.equals(ek)))
                    return e.val;
            }
            if (eh < 0)
                return (p = e.find(h, key)) != null ? p.val : null;
            while ((e = e.next) != null) {
                if (e.hash == h &&
                    ((ek = e.key) == key || (ek != null && key.equals(ek))))
                    return e.val;
            }
        }
        return null;
    }
    
    • 无锁读取:读取操作不需要加锁,依赖于 volatile 变量的可见性来保证线程安全。

# 5. ConcurrentHashMap 的应用场景

  • 缓存系统:ConcurrentHashMap 常用于缓存系统中,允许多个线程同时访问缓存数据,适用于读取频繁的场景,例如本地缓存的实现。
  • 频率统计:在高并发环境中,ConcurrentHashMap 可用于实现数据统计和频率计数,能够快速、安全地对数据进行更新。
  • 共享配置存储:在多线程环境中,ConcurrentHashMap 可用于存储共享配置,多个线程可以同时读取或更新配置项。

# 6. ConcurrentHashMap 与其他 Map 的比较

  • 与 HashMap:HashMap 在多线程环境下是不安全的,容易导致数据不一致,而 ConcurrentHashMap 通过分段锁和 CAS 实现了线程安全,可以在高并发环境下使用。
  • 与 Hashtable:Hashtable 使用同步锁来保证线程安全,每次读写操作都会锁住整个对象,这在高并发环境中性能较差。ConcurrentHashMap 则使用细粒度的锁和无锁读取,在性能上远优于 Hashtable。
  • 与 SynchronizedMap:Collections.synchronizedMap() 返回的同步 Map 通过在每个方法上加锁来实现线程安全,类似于 Hashtable,但并发性能较低。ConcurrentHashMap 提供更高效的并发访问机制。

# 7. 总结

ConcurrentHashMap 是 Java 并发编程中不可或缺的数据结构,通过细粒度的锁和无锁的读操作,实现了高效的并发访问。在多线程环境下使用 ConcurrentHashMap 可以有效避免数据竞争问题,同时提供比传统同步容器更高的性能。在实际开发中,ConcurrentHashMap 常用于需要高效并发访问和修改的场景,例如缓存、频率统计等。理解其内部机制和应用场景,可以帮助开发者在多线程编程中编写更加高效、安全的代码。

上次更新: 2024/11/01, 13:45:14
并发容器
BlockingQueue

← 并发容器 BlockingQueue→

Copyright © 2023-2024 Wray | 鄂ICP备2024050235号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式