`
chenzehe
  • 浏览: 532389 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

Java 并发集合ConcurrentHashMap

 
阅读更多

ConcurrentHashMap是JDK1.5并发包中提供的线程安全的HashMap的实现,其包结构关系如下:

public class ConcurrentHashMap<K, V> extends AbstractMap<K, V>
        implements ConcurrentMap<K, V>, Serializable {
}
public abstract class AbstractMap<K,V> implements Map<K,V> {
}
public interface ConcurrentMap<K, V> extends Map<K, V> {
}

 ConcurrentHashMap实现并发是通过“锁分离”技术来实现的,也就是将锁拆分,不同的元素拥有不同的锁,ConcurrentHashMap内部使用段(Segment)来表示这些不同的部分,其中每一个片段是一个类似于HashMap的结构,它有一个HashEntry的数组,数组的每一项又是一个链表,通过HashEntry的next引用串联起来,它们有自己的锁。

final Segment<K,V>[] segments;

 Segment继承自ReentrantLock,在创建Segment对象时,其所做的动作就是创建一个指定大小为cap的HashEntry对象数组,并基于数组的大小及loadFactor计算threshold的值:threshold = (int)(newTable.length * loadFactor);

        Segment(int initialCapacity, float lf) {
            loadFactor = lf;
            setTable(HashEntry.<K,V>newArray(initialCapacity));
        }
        void setTable(HashEntry<K,V>[] newTable) {
            threshold = (int)(newTable.length * loadFactor);
            table = newTable;
        }

 

构造函数

public ConcurrentHashMap(int initialCapacity,
                         float loadFactor,
                         int concurrencyLevel)创建一个带有指定初始容量、加载因子和并发级别的新的空映射。 

参数:
initialCapacity - 初始容量。该实现执行内部大小调整,以容纳这些元素。
loadFactor - 加载因子阈值,用来控制重新调整大小。在每 bin 中的平均元素数大于此阈值时,可能要重新调整大小。
concurrencyLevel - 当前更新线程的估计数。该实现将执行内部大小调整,以尽量容纳这些线程。 
抛出: 
IllegalArgumentException - 如果初始容量为负,或者加载因子或 concurrencyLevel 为非正。

    public ConcurrentHashMap(int initialCapacity,
                             float loadFactor, int concurrencyLevel) {
        if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0)
            throw new IllegalArgumentException();

        if (concurrencyLevel > MAX_SEGMENTS)
            concurrencyLevel = MAX_SEGMENTS;

        // Find power-of-two sizes best matching arguments
        int sshift = 0;
        int ssize = 1;
        while (ssize < concurrencyLevel) {
            ++sshift;
            ssize <<= 1;
        }
        segmentShift = 32 - sshift;
        segmentMask = ssize - 1;
        this.segments = Segment.newArray(ssize);

        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        int c = initialCapacity / ssize;
        if (c * ssize < initialCapacity)
            ++c;
        int cap = 1;
        while (cap < c)
            cap <<= 1;

        for (int i = 0; i < this.segments.length; ++i)
            this.segments[i] = new Segment<K,V>(cap, loadFactor);
    }

 基于如下方法计算ssize的大小:

        int sshift = 0;
        int ssize = 1;
        while (ssize < concurrencyLevel) {
            ++sshift;
            ssize <<= 1;
        }

 默认情况下构造函数的三个值分别为16、0.75f、16。在concurrencyLevel为16的情况下,计算出的ssize值为16,并使用该值作为参数传入Senment的newArray方法创建一个大小为16的Segment对象数组,也就是默认情况下ConcurrentHashMap是用了16个类似HashMap 的结构。

采用下面方法计算cap变量的值:

        int c = initialCapacity / ssize;
        if (c * ssize < initialCapacity)
            ++c;
        int cap = 1;
        while (cap < c)
            cap <<= 1;

 算出的cap为1。

 

 put(Object key,Object value)方法

 ConcurrentHashMap的put方法并没有加synchronized来保证线程同步,而是在Segment中实现同步,如下:

 

    public V put(K key, V value) {
        if (value == null)
            throw new NullPointerException();
        int hash = hash(key.hashCode());
        return segmentFor(hash).put(key, hash, value, false);
    }

//下面为Segment的put方法
V put(K key, int hash, V value, boolean onlyIfAbsent) {
            lock();
            try {
                int c = count;
                if (c++ > threshold) // ensure capacity
                    rehash();
                HashEntry<K,V>[] tab = table;
                int index = hash & (tab.length - 1);
                HashEntry<K,V> first = tab[index];
                HashEntry<K,V> e = first;
                while (e != null && (e.hash != hash || !key.equals(e.key)))
                    e = e.next;

                V oldValue;
                if (e != null) {
                    oldValue = e.value;
                    if (!onlyIfAbsent)
                        e.value = value;
                }
                else {
                    oldValue = null;
                    ++modCount;
                    tab[index] = new HashEntry<K,V>(key, hash, first, value);
                    count = c; // write-volatile
                }
                return oldValue;
            } finally {
                unlock();
            }
        }
 ConcurrentHashMap不能保存value为null值,否则抛出NullPointerException,key也不能为空:

 

 

int hash = hash(key.hashCode());
在HashMap中,null可以作为key也可以为value。和HashMap一样,首先对key.hashCode()进行hash操作,得到key的hash值,然后再根据hash值得到其对应数组的Segment对象,接着调用Segment对象的put方法来完成操作。当调用Segment对象的put方法时,先进行lock操作,接着判断当前存储的对象个数加1后是否大于threshold,如大于则将当前的HashEntry对象数组大小扩大两倍,并将之前存储的对象重新hash转移到新的对象数组中。接下去的动作和HashMap基本一样,通过对hash值和对象数组大小减1进行按位与操作后,找到当前key要存放的数组的位置,接着寻找对应位置上的HashEntry对象链表是否有key、hash值和当前key相同的,如果有则覆盖其value,如果没有则创建一个新的HashEntry对象,赋值给对应位置的数组对象,构成链表。
从上面可以看出ConcurrentHashMap基于concurrencyLevel划分出多个Segment来存储对象,从则避免每次put操作都锁住得锁住整个数组。在默认的情况下可以充许16个线程并发无阻塞的操作集合对象。
remove(Object key)方法:
    public V remove(Object key) {
	int hash = hash(key.hashCode());
        return segmentFor(hash).remove(key, hash, null);
    }
 首先对key进行hash求值,再根据hash值找到对应的Segment对象,调用其remove方法完成删除操作。remove方法跟put方法一样,也是在Segment对象中的方法才加锁。
get(Object key)方法:
 跟put和remove方法一样,首先对key进行hash,再根据该hash值找到对应的Segment对象,然后调用该Segment对象的get方法完成操作。
    public V get(Object key) {
        int hash = hash(key.hashCode());
        return segmentFor(hash).get(key, hash);
    }
Segment的get方法先判断当前HashEntry对象数组的长度是否为0,如果为0则直接返回null。然后用hash值和对象数组长度减1按位与操作得到该位置上的HashEntry对象,然后再遍历该HashEntry对象,如果value不为空,则直接返回value,如果为null,则调用readValueUnderLock()方法取得value并返回,下面方法为Segment的get方法:
        V get(Object key, int hash) {
            if (count != 0) { // read-volatile
                HashEntry<K,V> e = getFirst(hash);
                while (e != null) {
                    if (e.hash == hash && key.equals(e.key)) {
                        V v = e.value;
                        if (v != null)
                            return v;
                        return readValueUnderLock(e); // recheck
                    }
                    e = e.next;
                }
            }
            return null;
        }
        V readValueUnderLock(HashEntry<K,V> e) {
            lock();
            try {
                return e.value;
            } finally {
                unlock();
            }
        }
 从上面可以看出,ConcurrentHashMap的get方法仅在找到value为null时才加锁,其它情况下都不加锁。
get方法首先通过hash值和HashEntry对象数组大小减1按位与来获取对应位置的HashEntry,在这个步骤中可能因为数组大小的改变而导致获取HashEntry数组对象位置出错,ConcurrentHashMap通过把HashEntry数组对象定义为volatile类型来保证线程同步。
        transient volatile HashEntry<K,V>[] table;
        HashEntry<K,V> getFirst(int hash) {
            HashEntry<K,V>[] tab = table;
            return tab[hash & (tab.length - 1)];
        }
在获取到HashEntry对象后,怎么保证它及其next属性构成的链表上的对象不会改变呢?ConcurrentHashMap中是把HashEntry对象中的hash、key、以及next属性都是final的,也意味着没办法插入一个HashEntry对象到HashEntry基于next属性构成的链表中间或末尾(与HashMap一样,新插入的对象也是插入到HashEntry的表头)。
 和HashMap性能比较
在单线程情况下,ConcurrentHashMap比HashMap性能稍微差一点,在多线程情况下,随着线程数量的增加,ConcurrentHashMap性能明显比HashMap提升,特别是查找性能,而且随着线程数量的增加,ConcurrentHashMap性能并没有出现下降的情况,所以在并发的场景中,使用ConcurrentHashMap比使用HashMap是更好的选择。
 
分享到:
评论

相关推荐

    java集合-ConcurrentHashMap的使用

    ConcurrentHashMap使用了分段锁(Segment)来实现并发的读写操作,每个Segment都相当于一个小的HashMap,将...因此,如果需要保证精确的操作顺序或避免并发更新带来的问题,可以考虑使用更高级的同步工具或并发集合类。

    Java并发编程基础.pdf

    并发集合:熟悉Java提供的并发集合类,如ConcurrentHashMap、CopyOnWriteArrayList等,这些集合类在并发环境下提供了线程安全的操作。 线程池:了解Java中的线程池概念,如ExecutorService和ThreadPoolExecutor,...

    Java 多线程与并发(13-26)-JUC集合- ConcurrentHashMap详解.pdf

    Java 多线程与并发(13_26)-JUC集合_ ConcurrentHashMap详解

    java并发编程综合讲解

    其次,我们将介绍并发集合类的使用。您将了解如何在多线程环境下安全地进行数据访问,以及如何避免并发访问所带来的问题。我们将详细介绍 JUC 提供的线程安全集合类,如 ConcurrentHashMap 和 ConcurrentLinkedQueue...

    java高并发相关知识点.docx

    并发集合:Java中的并发集合,包括ConcurrentHashMap、ConcurrentLinkedQueue、CopyOnWriteArrayList等。 并发控制:Java中的并发控制机制,包括信号量、原子变量、倒计时等。 线程安全:Java中的线程安全,包括同步...

    Java 集合方面的面试题

    以下是一些 Java 集合方面的面试题: Java 中集合框架的主要接口是什么? ArrayList 和 LinkedList 有什么区别? HashSet 和 TreeSet 有什么区别? HashMap 和 TreeMap 有什么区别? 什么是迭代器?如何使用它来遍历...

    常用Java代码65个附示例代码

    7.Java中的并发集合(ConcurrentHashMap、CopyOnWriteArrayList等) 8.Java中的Future和Callable接口 9.Java中的异常传播 10.Java中的断言(Assertions) 11.Java中的泛型(Generics) 12.Java中的反射(Reflection...

    「Java学习+面试指南」一份涵盖大部分 Java 程序员所需要掌握的核心知识 准备 Java 面试,首选.zip

    Java 集合常见知识点&面试题总结(上) (必看 ) Java 集合常见知识点&面试题总结(下) (必看 ) Java 容器使用注意事项总结 源码分析 : ArrayList 源码+扩容机制分析 HashMap(JDK1.8)源码+底层数据结构分析 ...

    「Java学习+面试指南」一份涵盖大部分 Java 程序员所需要掌握的核心知识

    Java 集合常见知识点&面试题总结(上) (必看 ) Java 集合常见知识点&面试题总结(下) (必看 ) Java 容器使用注意事项总结 源码分析: ArrayList 源码+扩容机制分析 HashMap(JDK1.8)源码+底层数据结构分析 ...

    java编发编程:JUC综合讲解

    JUC 提供了线程安全的并发集合类,如 ConcurrentHashMap、ConcurrentLinkedQueue 等。 3. 原子操作(Atomic Operations): 原子操作是不可再分割的基本操作,JUC 提供了一系列原子操作类,如 AtomicInteger、...

    Java多线程和并发知识整理

    一、理论基础 ... 6.3 并发集合 6.4 原子类 6.5 线程池 七、BlockingQueue 八、ConcurrentHashMap 九、CopyOnWriteArrayList 十、原子类-CAS, Unsafe和原子类详解 十一、JUC锁: LockSupport详解

    JUC多线程学习个人笔记

    并发集合:JUC提供了一些线程安全的集合类,如ConcurrentHashMap、ConcurrentLinkedQueue等,可以在多线程环境下安全地访问和修改集合。 原子操作:JUC提供了一些原子操作类,如AtomicInteger、AtomicLong等,可以...

    Java Core Sprout:基础、并发、算法

    常用集合 数组列表/向量 链表 哈希映射 哈希集 链接哈希映射 Java多线程 多线程中的常见问题 同步关键字原理 多线程的三大核心 对锁的一些认知 ReentrantLock实现原理 ConcurrentHashMap 的实现原理 如何优雅地使用...

    Java后端面试问题整理.docx

    • 熟悉常用集合数据结构(数组、Hashmap、ConcurrentHashMap、HashTable、ArrayList、Vetor、LinkedList、HashSet、TreeSet、LinkedHashSet),了解AVL、RBtree、B/B+树、跳表 • 熟悉常见异常分类以及处理,熟悉反射...

    triemap:Scala集合库中并发trie哈希映射实现的Java端口

    关于这是Scala集合库中并发的trie哈希映射实现的Java端口。 它曾经是从Scala到Java的几乎逐行转换。 如今,它已经被重构为对Java 8友好,并且无法通过重构来进行一些原始的断言。 在Aleksandar Prokopec撰写的这些...

    java7hashmap源码-java-note:笔记

    集合 ConcurrentHashMap IO 并发 [Java 并发]( 并发.md) AQS 源码 JVM web 基础 [NGINX 简介](./docs/nginx/NGINX 简介.md) 框架 Spring [观察 Spring bean 实例化](./docs/spring/观察 Spring bean 实例化.md) ...

    Java开发常见问题总结.docx

    对于并发环境,使用线程安全的集合类如ConcurrentHashMap、CopyOnWriteArrayList等。 避免在循环中修改集合,可能导致ConcurrentModificationException。 异常处理: 不要忽视异常,合理捕获并处理它们。 不要过度...

    JAVA面试常见问题整理

    在Java集合框架方面,文章介绍了List、Set和Map的区别和特点,以及它们的使用场景。接下来,文章重点讲解了ConcurrentHashMap的原理和实现方式,以及ThreadLocal的原理和使用场景。 最后,文章简要概述了Spring框架...

    第10讲 如何保证集合是线程安全的 ConcurrentHashMap如何实现高效地线程安全1

    介绍完整的,算是个开胃菜吧,类似CAS等更加底层的机制,后面会在Java进阶模块中的并发主题有更加系统的介绍。知识扩展1. 为什么需要 ConcurrentHa

    免费开源!!Java Core Sprout:基础、并发、算法

    常用集合 数组列表/向量 链表 哈希映射 哈希集 链接哈希映射 Java多线程 多线程中的常见问题 同步关键字原理 多线程的三大核心 对锁的一些认知 ReentrantLock实现原理 ConcurrentHashMap 的实现原理 如何优雅地使用...

Global site tag (gtag.js) - Google Analytics