`

集合框架 Map篇(4)----ConcurrentHashMap

 
阅读更多
Map
------


1.HashMap
2.LinkedHashMap
3.IdentityHashMap
4.WeakHashMap
5.TreeMap
6.EnumMap
7.ConcurrentHashMap
8.ConcurrentSkipListMap
--------------------------------------

------------------ConcurrentHashMap-----------------

ConcurrentHashMap是一个线程安全的哈希表。ConcurrentHashMap可以做到读取数据不加锁,并且其内部的结构可以让其在进行写操作的时候能够将锁的粒度保持地尽量地小,不用对整个ConcurrentHashMap加锁。

ConcurrentHashMap存储结构

ConcurrentHashMap为了提高本身的并发能力,在内部采用了一个叫做Segment(分段)的结构,ConcurrentHashMap内部有一个Segment的数组实现的哈希表。每个Segment又包含一个Entry数组实现的hash表(这个hash 表类似于HashMap中的实现方式),结构图如下:
      
                                                                

ConcurrentHashMap存储结构图

   从上面的结构我们可以了解到,ConcurrentHashMap定位一个元素的过程需要进行两次Hash操作,第一次Hash定位到Segment,第二次Hash定位到元素所在的链表的头部。
  因此,这一种结构的带来的副作用是Hash的过程要比普通的HashMap要长,但是带来的好处是写操作的时候可以只对元素所在的Segment进行加锁即可,不会影响到其他的Segment,这样,在最理想的情况下,ConcurrentHashMap可以最高同时支持Segment数量大小的写操作(当且仅当这些写操作都非常平均地分布在所有的Segment上时),所以,通过这一种结构,ConcurrentHashMap的并发能力可以大大的提高。

Segment 的具体实现
// Segment是ReentrantLock的子类实现,这样方便锁的管理
static final class Segment<K,V> extends ReentrantLockimplements Serializable {
    //该片段(Segment)中元素的个数,注意volatile修饰符(保证了count的修改可见性)
    transientvolatileint count;
    //对Segment中的table修改的次数
    transient int modCount; 
    //临界值,当Segment中的元素个数大于该值是,就对Segment的table进行扩容
    transient int threshold;   
    transient volatile HashEntry<K,V>[] table;// Segment中的hash表结构
    final float loadFactor;//负载因子=元素个数和表长的比值
    get(){…}
    Put(){…}
    …
} 


HashEntry的具体实现
/**
*与HashMap中的Entry实现还是有较大区别的:
*1.不是Map.Entry的子类实现,没有重写equals/hashCode方法;
*2.value 是volatile类型的(保证了修改的可见性)
*3.next是final类型的,意味着next引用一经赋值是不可变的
*/
   static final class HashEntry<K,V> {
        final K key;
        final int hash;
        volatile V value;
        final HashEntry<K,V> next;

        HashEntry(K key, int hash, HashEntry<K,V> next, V value) {
            this.key = key;
            this.hash = hash;
            this.next = next;
            this.value = value;
        }

 @SuppressWarnings("unchecked")
 static final <K,V> HashEntry<K,V>[] newArray(int i) {
     return new HashEntry[i];
 }
}

ConcurrentHashMap初始化
/**
* ConcurrentHashMap初始化需要3个参数:
*1. initialCapacity:表示 初始化容量;
*2. loadFactor:表示 负载因子
*3. concurrencyLevel表示 片段(Segment)的数量
*/
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;

        //找到大于concurrencyLevel(分段个数)的最小的2的指数:
        //例如concurrencyLevel=12,ssize=16
        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;//把总的容量,平均分成ssize段
        if (c * ssize < initialCapacity)
            ++c;
        int cap = 1;
        while (cap < c)  //得到每段的2的指数的容量(也就是Segment的容量)
            cap <<= 1;

        for (int i = 0; i < this.segments.length; ++i)//初始化segments数组
            this.segments[i] = new Segment<K,V>(cap, loadFactor);
    }
Segment的数量以及Segment的容量都是2的指数,这样的好处就是方便通过移位的方式计算hash,提高了hash效率。

ConcurrentHashMap的get操作
/**
*分两步:1.得到segment 段 的index
*        2.得到所在段的 entry的index
* 所用的hash值相同,只不过通过取不同的位来确定段index和Entry的index
*/
public V get(Object key) {
     int hash = hash(key.hashCode());
     return segmentFor(hash).get(key, hash);
}

/*
*得到段的Index
*假设segment的数量为2^p,则:
*segmentShift = 32 - p; segmentMask = 2^p - 1;
*通过无符号右移hash值得到其高p位&0111111…1(其中1的个数为p-1个),
*得到[0~2^p-1]直接的index作为segment数组的下标
*/
final Segment<K,V> segmentFor(int hash){
     return segments[(hash >>> segmentShift) & segmentMask];
}
确定段后,再调用该段的get方法以获得entry的index:
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;
                            //只有在value为空的情况(其他线程正执行put操作),
                            //才进入加锁获取
                        return readValueUnderLock(e);                 
                     }
                    e = e.next;
                }
            }
            return null;
 }
得到key位置的链表的头位置index方法如下:
HashEntry<K,V> getFirst(int hash) {
       HashEntry<K,V>[] tab = table;
       return tab[hash & (tab.length - 1)];
}
ConcurrentHashMap的put操作
//同样是先找到segment,然后调用segment的put方法
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方法,整个方法在加锁的情况下进行
//(该锁紧针对所在的segment分段,而不是整个Map)
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;
                //通过key的hash定位到table中的index处得到链结构的头结点
                int index = hash & (tab.length - 1);
                HashEntry<K,V> first = tab[index];
                HashEntry<K,V> e = first;
                //遍历链表寻找key对应的Entry
                while (e != null && (e.hash != hash || !key.equals(e.key)))
                    e = e.next;
               
                V oldValue;
                if (e != null) {//如果有key对应的Entry,覆盖原来的值
                    oldValue = e.value;
                    if (!onlyIfAbsent)
                        e.value = value;
                }
                else {//没有对应的entry,新建entry置于table的index处作链头
                  oldValue = null;
                  ++modCount;
                  tab[index] = new HashEntry<K,V>(key, hash, first, value);
                  count = c; // write-volatile
                }
                return oldValue;
            } finally {
                unlock();
            }
 }
对Segment的put操作是加锁完成的,如果Segment中元素的数量超过了阈值(由构造函数中的loadFactor算出)需要进行对Segment扩容,并且要进行rehash.


ConcurrentHashMap的remove操作

remove操作的前面一部分和前面的get和put操作一样,都是定位Segment的过程,然后再调用Segment的remove方法:
//remove的操作也是在加锁的情况下进行
V remove(Object key, int hash, Object value) {
            lock();
            try {
                int c = count - 1;
                //通过key的hash定位到table中的index处得到链结构的头结点
                HashEntry<K,V>[] tab = table;
                int index = hash & (tab.length - 1);
                HashEntry<K,V> first = tab[index];
                HashEntry<K,V> e = first;
                //遍历链表寻找key对应的Entry
                while (e != null && (e.hash != hash || !key.equals(e.key)))
                    e = e.next;

                V oldValue = null;
                if (e != null) {
                    V v = e.value;
                    if (value == null || value.equals(v)) {/
                      oldValue = v;
                      ++modCount;
                      
                      HashEntry<K,V> newFirst = e.next;
                      for (HashEntry<K,V> p = first; p != e; p = p.next)
                        newFirst = new HashEntry<K,V>(p.key, p.hash, newFirst, p.value);

                      tab[index] = newFirst;
                      count = c; // write-volatile
                    }
                }
                return oldValue;
            } finally {
                unlock();
            }
}
执行删除操作也是在加锁的情况下:e后边的next关系不变;/重头结点开始遍历e前面的元素,最终把e前面的结点next关系//翻转,并把翻转后的第一个结点(也就是翻转前,e的“前驱”)作为了新链的头,保存到table的index处。下图以删除E3为例进行了演示:



ConcurrentHashMap的获取size()方法
上面的get/put/remove操作都是在单个Segment中进行的,但是ConcurrentHashMap有一些操作是在多个Segment中进行,比如size操作,ConcurrentHashMap的size操作也采用了一种比较巧的方式,来尽量避免对所有的Segment都加锁。
//size()方法的实现
public int size() {
        final Segment<K,V>[] segments = this.segments;
        long sum = 0;
        long check = 0;
        int[] mc = new int[segments.length];
        // Try a few times to get accurate count. On failure due to
        // continuous async changes in table, resort to locking.
        for (int k = 0; k < RETRIES_BEFORE_LOCK; ++k) {
            check = 0;//第二遍 遍历segments得到的总元素个数
            sum = 0; //第一遍 遍历segments得到的总元素个数
            int mcsum = 0;//改变的次数
            for (int i = 0; i < segments.length; ++i) {
                sum += segments[i].count;
                mcsum += mc[i] = segments[i].modCount;
            }
            if (mcsum != 0) {//有改变的情况下才如下操作,否则直接下一步
                for (int i = 0; i < segments.length; ++i) {
                    check += segments[i].count;
                    //如果任一segment的modCoun有变化,
                    //结束操作,进入下一遍历比较
                    if (mc[i] != segments[i].modCount) {
                        check = -1; // force retry
                        break;
                    }
                }
            }
            if (check == sum)
                break;
        }
        if (check != sum) { // 依赖锁住所有的segment,然后一一计算相加
            sum = 0;
            for (int i = 0; i < segments.length; ++i)//所有的segment加锁
                segments[i].lock();
            for (int i = 0; i < segments.length; ++i)
                sum += segments[i].count;
            for (int i = 0; i < segments.length; ++i) //所有的segment解锁
                segments[i].unlock();
        }
        if (sum > Integer.MAX_VALUE)
            return Integer.MAX_VALUE;
        else
            return (int)sum;
    }
Size()操作就是遍历了两次Segment,每次记录Segment的count值,然后将两次的count进行比较,如果相同,则表示期间没有发生过修改操作,就将原先遍历的结果返回,如果不相同,则把这个过程再重复做一次(重复检查的次数是有Map的常量static final RETRIES_BEFORE_LOCK=2决定的),如果再不相同,则就需要将所有的Segment都锁住,然后一个一个遍历了。



  • 大小: 60.8 KB
  • 大小: 40 KB
分享到:
评论

相关推荐

    20-集合框架020-HashMap-1080P 高清-AVC20

    在Java编程语言中,集合框架是一个非常重要的组成部分,它提供了数据结构和算法的实现,使得在处理各种数据存储和操作时更加高效和便捷。在这个主题中,我们将深入探讨HashMap类,它是Java集合框架中的一个关键组件...

    Java集合框架常见面试题.pdf

    Java集合框架主要包括Collection接口和Map接口两大分支。Collection接口主要包括List、Set以及Queue三个子接口,而Map接口则用于存储键值对映射。 1. Collection接口与Map接口的区别: - Collection接口是单列集合...

    Java集合框架Map接口.pdf

    Java集合框架中的Map接口是Java编程中非常重要的一个部分,它提供了一种存储键值对数据的方式。在Map中,每个键(key)都是唯一的,用于标识对应的值(value),而值可以重复出现。这种数据结构广泛应用于各种场景,...

    Java 集合框架(1-9)-Collection 类关系图.pdf

    在Java集合框架中,有两个主要的接口:`Collection`和`Map`。 `Collection`接口是所有单值容器的父接口,它分为三个主要子接口:`Set`、`List`和`Queue`。 1. `Set`接口: - `TreeSet`:基于红黑树实现,提供有序...

    集合框架学习笔记

    这篇学习笔记将深入探讨Java集合框架的基础概念、主要类库以及常见应用场景。 首先,Java集合框架分为两种基本类型:List(列表)和Set(集)。List接口代表有序的集合,允许重复元素,如ArrayList和LinkedList;而...

    集合框架总结图

    在Java集合框架中,主要有两种类型的容器:集合(Collection)和映射(Map)。集合是用来存储一组不重复元素的容器,而映射则用于存储键值对,其中键是唯一的。 1. 集合框架层次结构: - **接口**:集合框架的核心...

    Java面向对象程序设计-集合框架Map接口.pptx

    在处理复杂数据存储时,集合框架是必不可少的工具,而Map接口则是集合框架中的一个重要组成部分。Map接口定义了键值对(key-value pairs)的数据结构,使得我们可以根据键来高效地查找对应的值。 在农业信息系统...

    区别和联系-list-map-set-vector

    List 是 Java 集合框架中的一个接口,它允许元素重复,并且每个元素都有一个特定的顺序位置。这意味着可以通过索引来访问 List 中的元素。List 提供了多种实现类,包括 `LinkedList`、`ArrayList`、`Vector` 和 `...

    java集合框架专题-java面试经典

    ### Java集合框架专题详解 #### 一、Java集合框架概览 Java集合框架是一个用于存储和操作对象集合的标准API。该框架提供了多种容器类型,包括`Collection`和`Map`两大类。 - **Collection**:这是一个接口,表示...

    Map-main-源码.rar

    Map接口是Java集合框架中的重要组成部分,它提供了一种存储键值对数据结构的方式,使得我们可以通过键(Key)快速查找对应的值(Value)。在Java中,有许多实现了Map接口的类,如HashMap、TreeMap、LinkedHashMap等...

    Java集合框架面试题

    "Java集合框架面试题" Java 集合框架是 Java 语言中的一组预定义类和接口的集合,用于存储和操作数据。下面是 Java 集合框架的知识点总结: 1. Java 集合类主要有两大分支:Collection 接口和 Map 接口。...

    学士后Java集合框架和泛型课后习题答案

    1. **集合接口**:List、Set、Map是Java集合框架的三大接口。List接口代表有序且可重复的元素序列,如ArrayList和LinkedList;Set接口代表无序且不允许重复元素的集合,如HashSet和TreeSet;Map接口则存储键值对,如...

    Java集合框架详解

    集合框架是一个统一的数据结构模型,它定义了一系列接口,如Collection、List、Set和Map,以及它们的实现类,如ArrayList、LinkedList、HashSet、HashMap等。容器是这些接口和类的统称,它们用于存储和管理对象。...

    Java集合类List-Set-Map的区别和联系.doc

    Java集合框架是编程中不可或缺的一部分,它提供了多种数据结构,如List、Set和Map,用于存储和管理对象。下面我们将详细探讨这些集合类的区别、联系以及何时选择它们。 首先,数组(Array)是最基础的数据结构,它...

    Java集合框架常见面试题

    Java 集合框架常见面试题 Java 集合框架是 Java 语言中最重要的组件之一,集合框架提供了许多有用的功能来存储和操作数据。在面试中,Java 集合框架的问题经常被问到,因此了解 Java 集合框架的基本概念和实现机制...

    集合框架源码分析

    Java集合框架主要包括`Collection`、`List`、`Set`和`Map`四大接口。`Collection`是最基本的接口,`List`和`Set`继承自它。`List`接口保持元素的顺序,并允许重复元素;`Set`接口则不允许重复元素,且不保证元素的...

    advanced-java-collections:高级Java课程-集合框架的源代码-Source code collection

    Java集合框架中的`ConcurrentHashMap`和`CopyOnWriteArrayList`等类设计用于多线程环境,提供了线程安全的访问。 5. **源码分析**: 深入源码可以发现,例如ArrayList的扩容策略、HashMap的冲突解决以及...

    java 集合框架

    1. **灵活性**:集合框架支持多种数据结构,包括但不限于List(有序且允许重复)、Set(无序且不允许重复)、Map(键值对存储)等,满足不同场景下的数据存储需求。 2. **高效性**:内置的集合类通常经过优化,提供...

Global site tag (gtag.js) - Google Analytics