深入ConcurrentHashMap一

ConcurrentHashMap能够做到比较高性能的并发访问。

ConcurrentHashMap内部有一个Segment数组,每个Segment有一个lock。

Segment相当于是一个子map,拥有一个HashEntity数组。

这样可以将并发压力分摊到多个Segment上。

ConcurrentHashMap组成图:

这里的Segment个数必须为2的n次方,为了之后高效计算要存放的key存放在哪个Segment上(用  << 实现)。

Segment内部拥有一个类型为volatile的HashEntry数组,这是为了能够让其它线程看到最新的值。

ConcurrentHashMap拥有put,get,remove等操作。而原有的扩容操作,为了性能则只针对单个Segment来进行。

关键的Segment相当了ConcurrentHashMap中的子map,它包含了一个HashEntry数组。它的类图如下:

这里关键的Segment类图如下:

HashEntry内部存储了hash值,final的key值,及volatile的value及next值。

这里value及next值是volatile是为实现get操作的不需要加锁提供了基础。

HashEntry类图如下:

下面分别从ConcurrentHashMap创建,往ConcurrentHashMap放入元素,从ConcurrentHashMap取出元素来进行分析。

一.ConcurrentHashMap创建

在用ConcurrentHashMap时,可以在构造函数中传入你想要的map容量,loadFactor装载因子,并发数(大致决定了有几个Segment)。

如果不传入,使用空构造函数时,默认的map容量大小为16,loadFactor为0.75,并发数为16。

我们以默认的参数开始分析。

首先需要确定总容量大小(这里指所有Segment中存放数组元素的数组大小总和),Segment总数,以及每个Segment中HashEntry数组的大小。

我们先附上代码:

 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;
        }
        this.segmentShift = 32 - sshift;
        this.segmentMask = ssize - 1;
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        int c = initialCapacity / ssize;
        if (c * ssize < initialCapacity)
            ++c;
        int cap = MIN_SEGMENT_TABLE_CAPACITY;
        while (cap < c)
            cap <<= 1;
        // create segments and segments[0]
        Segment<K,V> s0 =
            new Segment<K,V>(loadFactor, (int)(cap * loadFactor),
                             (HashEntry<K,V>[])new HashEntry[cap]);
        Segment<K,V>[] ss = (Segment<K,V>[])new Segment[ssize];
        UNSAFE.putOrderedObject(ss, SBASE, s0); // ordered write of segments[0]
        this.segments = ss;
 }

可以看到,对于Segment个数ssize取的是以concurrencyLevel为上界的2的n次方,这里由于concurrencyLevel为16,所以ssize也为16。

然后计算segmentShift,这个值用于后续在存放元素到ConcurrentHashMap时,用于决定有多少位hash高位参与计算存放元素的Segment数组下标。

这里segmentShift为32-sshift。sshift取的是Segment个数ssize的2的n次数的n值,即在计算ssize时左移的次数,这里为4。

所以segmentShift为28。

接着计算出segmentMask,这个值用于在计算得到存放元素的Segment所在数组的下标,这里为了效率使用 & 操作来替代%模操作。

这里由于Segment个数为16,所以segmentMask为15。

之后会计算出每个Segment的数组容量,这里先计算出每个Segment的大致大小,即用int c = initialCapacity / ssize; 来得出c,这里c为1。

之后会初始化一个cap作为真正的segment中HashEntry数组大小,将它初始化为2。

然后为了保证得出的segMent中HashEntry的大小为2的n次方,所以后续会对cap做以下操作:

while (cap < c)

cap <<= 1;

即cap的值要不是2(c值小于等于2时),或者是大于2的2的n次方。

得到最终每个Segment中HashEntry数组大小后,创建一个Segment数组,并在Segment数组0处初始化创建一个Segment。

最后将ConcurrentHashMap的segments设置为新创建的Segments数组。

这里可以看到最后将创建的Segment s0是调用UNSAFE.putOrderedObject(ss,SBASE,s0)来将其放入到ss数组中。

这里的UNSAFE.putOrderedObject是JAVA提供的用于直接操作内存的方法,其中参数ss是数组。

UNSAFE.putOrderedObject会延迟更新到内存中,但是由于后续在获取segment数组中的segment时,采用的是UNSAFE.getObjectVolatile,所以能够保证对于segment放入数组的操作对于后续的线程是可见的。

SBASE是ConcurrentHashMap的一个静态final long的值,相当于是Segment数组的首地址。

s0则是创建的需要放入ss数组中的segment实例。这里是将s0放入到ss数组位置0中。

如果提供类似:UNSAFE.putOrderedObject(ss,SBASE+offset,s)表明将元素s放入到数组ss的SBASE+offset位置。

其中这里的offset:

offset=步长*要放在数组第几位

步长一般是在编译期已经确定,这里测试过对于:Double或者Integer等,步长都是4。

深入ConcurrentHashMap一,布布扣,bubuko.com

时间: 2024-10-15 16:32:55

深入ConcurrentHashMap一的相关文章

ConcurrentHashMap 的实现原理

概述 我们在之前的博文中了解到关于 HashMap 和 Hashtable 这两种集合.其中 HashMap 是非线程安全的,当我们只有一个线程在使用 HashMap 的时候,自然不会有问题,但如果涉及到多个线程,并且有读有写的过程中,HashMap 就不能满足我们的需要了(fail-fast).在不考虑性能问题的时候,我们的解决方案有 Hashtable 或者Collections.synchronizedMap(hashMap),这两种方式基本都是对整个 hash 表结构做锁定操作的,这样在

Collections.synchronizedMap()与ConcurrentHashMap的区别

前面文章提到Collections.synchronizedMap()与ConcurrentHashM两者都提供了线程同步的功能.那两者的区别在哪呢?我们们先来看到代码例子.    下面代码实现一个线程对map进行写操作,另一个线程,读出并打印map数据. [java] view plain copy package test.map; import java.util.Collections; import java.util.HashMap; import java.util.Hashtab

HashMap,Hashtable,ConcurrentHashMap 和 synchronized Map 的原理和区别

HashMap 是否是线程安全的,如何在线程安全的前提下使用 HashMap,其实也就是HashMap,Hashtable,ConcurrentHashMap 和 synchronized Map 的原理和区别.当时有些紧张只是简单说了下HashMap不是线程安全的:Hashtable 线程安全,但效率低,因为是 Hashtable 是使用 synchronized 的,所有线程竞争同一把锁:而 ConcurrentHashMap 不仅线程安全而且效率高,因为它包含一个 segment 数组,将

【转】ConcurrentHashMap完全解析(JDK6/7、JDK8)

转自http://my.oschina.net/hosee/blog/675884 并发编程实践中,ConcurrentHashMap是一个经常被使用的数据结构,相比于Hashtable以及Collections.synchronizedMap(),ConcurrentHashMap在线程安全的基础上提供了更好的写并发能力,但同时降低了对读一致性的要求(这点好像CAP理论啊 O(∩_∩)O).ConcurrentHashMap的设计与实现非常精巧,大量的利用了volatile,final,CAS

java并发编程的艺术,读书笔记第六章 concurrentHashMap以及并发容器的介绍

ConcurrentHashMap的原理 将数据一段一段的存储然后给每一段数据分配一把锁,当线程访问数据的一段时,为每段分配一把锁,同时其他段的数据可以被其他线程数据访问 2)concurrentHashMap 的结构 concurrentHashMap 由segament数组和hashentry数组结构组成,segament是一种可靠的重入锁,在里面扮演锁的角色,hashentry用于存储键值对数据,一个segament里面包含一个hashentry数组,每个hashentry是一个链表结构的

Java中ConcurrentHashMap的实现

Java中ConcurrentHashMap的实现 ConcurrentHashMap(简写CHM)引入了分割,并提供了HashTable支持的所有的功能.在CHM中,支持多线程对Map做读操作,并且不需要任何的blocking.这得益于CHM将Map分割成了不同的部分,在执行更新操作时只锁住一部分.根据默认的并发级别(concurrency level),Map被分割成16个部分,并且由不同的锁控制.这意味着,同时最多可以有16个写线程操作Map.试想一下,由只能一个线程进入变成同时可由16个

ConcurrentHashMap总结

原文出处: Hosee 并发编程实践中,ConcurrentHashMap是一个经常被使用的数据结构,相比于Hashtable以及Collections.synchronizedMap(),ConcurrentHashMap在线程安全的基础上提供了更好的写并发能力,但同时降低了对读一致性的要求(这点好像CAP理论啊 O(∩_∩)O).ConcurrentHashMap的设计与实现非常精巧,大量的利用了volatile,final,CAS等lock-free技术来减少锁竞争对于性能的影响,无论对于

聊聊并发(四)——深入分析ConcurrentHashMap

线程不安全的HashMap 因为多线程环境下,使用HashMap进行put操作会引起死循环,导致CPU利用率接近100%,所以在并发情况下不能使用HashMap,如以下代码 final HashMap<String, String> map = new HashMap<String, String>(2); Thread t = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i &

Java并发编程:并发容器之ConcurrentHashMap

下面这部分内容转载自: http://www.haogongju.net/art/2350374 JDK5中添加了新的concurrent包,相对同步容器而言,并发容器通过一些机制改进了并发性能.因为同步容器将所有对容器状态的访问都 串行化了,这样保证了线程的安全性,所以这种方法的代价就是严重降低了并发性,当多个线程竞争容器时,吞吐量严重降低.因此Java5.0开 始针对多线程并发访问设计,提供了并发性能较好的并发容器,引入了java.util.concurrent包.与Vector和Hasht

探索 ConcurrentHashMap 高并发性的实现机制

简介 ConcurrentHashMap 是 util.concurrent 包的重要成员.本文将结合 Java 内存模型,分析 JDK 源代码,探索 ConcurrentHashMap 高并发的具体实现机制. 由于 ConcurrentHashMap 的源代码实现依赖于 Java 内存模型,所以阅读本文需要读者了解 Java 内存模型.同时,ConcurrentHashMap 的源代码会涉及到散列算法和链表数据结构,所以,读者需要对散列算法和基于链表的数据结构有所了解. Java 内存模型 由