jdk1.6源码学习---ConCurrentHashMap
ConcurrentHashMap源码阅读心得:
??? 在看这个源码之前应该先学习hashMap和hashTable的区别,首先我们应该了解到该类有什么作用:该类相对于hashMap而言具有同步map中的数据,对于hashtable而言,该同步数据对于并发程序提高了极高的效率,所以在使用缓存机制的时候如果对map中的值具有高并发的情况的话,那么我们就需要使用concurrentHashMap。
??? 首先了解下concurrentHashMap的原理:该类是通过存放一个Segments(段数组),然后在每个Segments[i]中放入一个类似hashMap的结构,具体看下图:
?这个结构的好处是我们在并发访问的时候只需要对同一个段里面的数据锁定就可以保持相对的同步性,但是在ConcurrentHashMap中却只对put,remvoe进行了同步锁,而对get方法却没有使用同步。使用了关键字volatile(下面会简单介绍下这个)这样提高了效率。
??? 首先我们看一下在ConcurrentHashMap.java中包含的三个类:
??? a.HashEntry:该类和HashMap中的Entry类似,唯一的却别在于构造函数,具体功能参考前章的hashmap和hashtable
final K key; final int hash; volatile V value;//该volatile的作用是保持主内存和线程中的值同步,每个线程获取这个值的时候都会拷贝这份,这样就可以解决读的同步问题 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; }?
??? b.segment:该类其实你可以看成是一个hashMap,它和hashMap的结构非常相似,里面也是一个数组。唯一的却别在于volatile int count,就是在统计每个segment(段)的存储数量的时候进行了同步。构造函数这些和hashMap基本一致(在这里不重复讲解) ??
??? c.ConcurrentHashMap:该类有4个构造方法分别,也是中间相互调用,看下最主要的构造函数:
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; //第一个参数是段里面的数组长度,第二个参数是效率因子(前面有讲到),第三个参数是段的长度 上面可以不用看。这里和hashMap的原理一样获取2的倍数来进行数组长度 int sshift = 0; int ssize = 1; while (ssize < concurrencyLevel) { ++sshift; ssize <<= 1;//向左移位 } segmentShift = 32 - sshift; segmentMask = ssize - 1; this.segments = Segment.newArray(ssize);//在这里创建了一个长度为ssize的数组,可见如果使用默认new concurrentHashMap的话段的长度将会永远固定,默认16个 //,也就是说最多只有16个线程进行并发,所以这个参数对并发是非常关键的 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);//创建每个段 }???? 和前面一样我们还是看最主要的方法:
??? put方法:该方法是在setment(段)里面放入键值(根据hashcode确定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();//因为segment是继承ReentrantLock类,该类可以实现同步锁功能和synxxx什么的有一点点区别(这里我不多讲,) try { int c = count; if (c++ > threshold) // 这个是却断段里面是否需要对数组进行扩容 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;//获取key-value的HashEntry 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);//值添加和hashMap一样 count = c; } return oldValue; } finally { unlock();//解锁 } }?
??? remove方法和put一样
??? 看一下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); //这个方法是当获得的value值为null的时候需要对其进行锁,这个好像是编译的时候初始化map会有问题。 } e = e.next; } } return null; }???? 该方法和hashmap基本没什么区别,所以对于concurrenthashmap而言它的读是异步了但是数据却保持了一致性这个就是volatile的好处,没有同步这样大大提高了效率
??? 下面还要介绍一下size方法,这个方法我觉得使用的很巧妙
public int size() { final Segment<K,V>[] segments = this.segments; long sum = 0;//计算concurrenthashmap中的个数 long check = 0; int[] mc = new int[segments.length];//统计所有段的修改次数 for (int k = 0; k < RETRIES_BEFORE_LOCK; ++k) {//进行2次非锁统计,如果还不行就把每个concurrenthashmap给锁了进行统计 check = 0; sum = 0; 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; if (mc[i] != segments[i].modCount) {//这里很巧妙,如果修改段操作刚好是一增一减,那还是保持不变所以不用重新统计。 check = -1; // force retry break; } } } if (check == sum)//如果验证结果和统计结果一致就输出总和 break; } if (check != sum) { // 如果2次统计还是不一致,则将每个段都锁了,再统计再进行解锁 sum = 0; for (int i = 0; i < segments.length; ++i) segments[i].lock(); for (int i = 0; i < segments.length; ++i) sum += segments[i].count; for (int i = 0; i < segments.length; ++i) segments[i].unlock(); } if (sum > Integer.MAX_VALUE) return Integer.MAX_VALUE; else return (int)sum; }?
以上草草讲了那么多,本人在研究这个类的时候花了一天的时间,学到了很多新名词和使用方法,比如volatile,ReentrantLock的lock方法,等等,可能中间还有不足之处,希望
多加指点,通过对这个类的学习,你可以对系统内存分配的性能进行调整,当你讲段分的越多的时候你系统的并发性将会大大的提高,这个需要根据实际情况而进行分配。多谢指点