注:在看这篇文章之前,如果对HashMap的层不清楚的话,建议先去看看HashMap源码解析。
http://www.cnblogs.com/java-zhao/p/5106189.html
1、对于ConcurrentHashMap需要掌握以下几点
2、ConcurrentHashMap的创建
注:在往下看之前,心里先有这样一个映像:ConcurrentHashMap的数据结构:一个指定个数的Segment数组,数组中的每一个元素Segment相当于一个HashTable。
2.1、使用方法:
Map<String, Object> map = new ConcurrentHashMap<String, Object>();
2.2、源代码:
ConcurrentHashMap相关属性:
/** * 用于分段 */ // 根据这个数来计算segment的个数,segment的个数是仅小于这个数且是2的几次方的一个数(ssize) static final int DEFAULT_CONCURRENCY_LEVEL = 16; // 最大的分段(segment)数(2的16次方) static final int MAX_SEGMENTS = 1 << 16; /** * 用于HashEntry */ // 默认的用于计算Segment数组中的每一个segment的HashEntry[]的容量,但是并不是每一个segment的HashEntry[]的容量 static final int DEFAULT_INITIAL_CAPACITY = 16; // 默认的加载因子(用于resize) static final float DEFAULT_LOAD_FACTOR = 0.75f; // 用于计算Segment数组中的每一个segment的HashEntry[]的最大容量(2的30次方) static final int MAXIMUM_CAPACITY = 1 << 30; /** * segments数组 * 每一个segment元素都看做是一个HashTable */ final Segment<K, V>[] segments; /** * 用于扩容 */ final int segmentMask;// 用于根据给定的key的hash值定位到一个Segment final int segmentShift;// 用于根据给定的key的hash值定位到一个Segment
Segment类(ConcurrentHashMap的内部类)
/** * 一个特殊的HashTable */ static final class Segment<K, V> extends ReentrantLock implements Serializable { private static final long serialVersionUID = 2249069246763182397L; transient volatile int count;// 该Segment中的包含的所有HashEntry中的key-value的个数 transient int modCount;// 并发标记 /* * 元素个数超出了这个值就扩容 threshold==(int)(capacity * loadFactor) * 值得注意的是,只是当前的Segment扩容,所以这是Segment自己的一个变量,而不是ConcurrentHashMap的 */ transient int threshold; transient volatile HashEntry<K, V>[] table;// 链表数组 final float loadFactor; /** * 这里要注意一个很不好的编程习惯,就是小写l,容易与数字1混淆,所以最好不要用小写l,可以改为大写L */ Segment(int initialCapacity, float lf) { loadFactor = lf;//每个Segment的加载因子 setTable(HashEntry.<K, V> newArray(initialCapacity)); } /** * 创建一个Segment数组,容量为i */ @SuppressWarnings("unchecked") static final <K, V> Segment<K, V>[] newArray(int i) { return new Segment[i]; } /** * Sets table to new HashEntry array. Call only while holding lock or in * constructor. */ void setTable(HashEntry<K, V>[] newTable) { threshold = (int) (newTable.length * loadFactor);// 设置扩容值 table = newTable;// 设置链表数组 }
说明:只列出了Segement的全部属性和创建ConcurrentHashMap时所用到的方法。
HashEntry类(ConcurrentHashMap的内部类)
/** * Segment中的HashEntry节点 类比HashMap中的Entry节点 */ static final class HashEntry<K, V> { final K key;// 键 final int hash;//hash值 volatile V value;// 实现线程可见性 final HashEntry<K, V> next;// 下一个HashEntry HashEntry(K key, int hash, HashEntry<K, V> next, V value) { this.key = key; this.hash = hash; this.next = next; this.value = value; } /* * 创建HashEntry数组,容量为传入的i */ @SuppressWarnings("unchecked") static final <K, V> HashEntry<K, V>[] newArray(int i) { return new HashEntry[i]; } }
ConcurrentHashMap(int initialCapacity,float loadFactor,int concurrencyLevel)
1 /** 2 * 创建ConcurrentHashMap 3 * @param initialCapacity 用于计算Segment数组中的每一个segment的HashEntry[]的容量, 但是并不是每一个segment的HashEntry[]的容量 4 * @param loadFactor 5 * @param concurrencyLevel 用于计算Segment数组的大小(可以传入不是2的几次方的数,但是根据下边的计算,最终segment数组的大小ssize将是2的几次方的数) 6 * 7 * 步骤: 8 * 这里以默认的无参构造器参数为例,initialCapacity==16,loadFactor==0.75f,concurrencyLevel==16 9 * 1)检查各参数是否符合要求 10 * 2)根据concurrencyLevel(16),计算Segment[]的容量ssize(16)与扩容移位条件sshift(4) 11 * 3)根据sshift与ssize计算将来用于定位到相应Segment的参数segmentShift与segmentMask 12 * 4)根据ssize创建Segment[]数组,容量为ssize(16) 13 * 5)根据initialCapacity(16)与ssize计算用于计算HashEntry[]容量的参数c(1) 14 * 6)根据c计算HashEntry[]的容量cap(1) 15 * 7)根据cap与loadFactor(0.75)为每一个Segment[i]都实例化一个Segment 16 * 8)每一个Segment的实例化都做下面这些事儿: 17 * 8.1)为当前的Segment初始化其loadFactor为传入的loadFactor(0.75) 18 * 8.2)创建一个HashEntry[],容量为传入的cap(1) 19 * 8.3)根据创建出来的HashEntry的容量(1)和初始化的loadFactor(0.75),计算扩容因子threshold(0) 20 * 8.4)初始化Segment的table为刚刚创建出来的HashEntry 21 */ 22 public ConcurrentHashMap(int initialCapacity,float loadFactor,int concurrencyLevel) { 23 // 检查参数情况 24 if (loadFactor <= 0f || initialCapacity < 0 || concurrencyLevel <= 0) 25 throw new IllegalArgumentException(); 26 27 if (concurrencyLevel > MAX_SEGMENTS) 28 concurrencyLevel = MAX_SEGMENTS; 29 30 /** 31 * 找一个能够正好小于concurrencyLevel的数(这个数必须是2的几次方的数) 32 * eg.concurrencyLevel==16==>sshift==4,ssize==16 33 * 当然,如果concurrencyLevel==15也是上边这个结果 34 */ 35 int sshift = 0; 36 int ssize = 1;// segment数组的长度 37 while (ssize < concurrencyLevel) { 38 ++sshift; 39 ssize <<= 1;// ssize=ssize*2 40 } 41 42 segmentShift = 32 - sshift;// eg.segmentShift==32-4=28 用于根据给定的key的hash值定位到一个Segment 43 segmentMask = ssize - 1;// eg.segmentMask==16-1==15 用于根据给定的key的hash值定位到一个Segment 44 this.segments = Segment.newArray(ssize);// 构造出了Segment[ssize]数组 eg.Segment[16] 45 46 /* 47 * 下面将为segment数组中添加Segment元素 48 */ 49 if (initialCapacity > MAXIMUM_CAPACITY) 50 initialCapacity = MAXIMUM_CAPACITY; 51 int c = initialCapacity / ssize;// eg.initialCapacity==16,c==16/16==1 52 if (c * ssize < initialCapacity)// eg.initialCapacity==17,c==17/16=1,这时1*16<17,所以c=c+1==2 53 ++c;// 为了少执行这一句,最好将initialCapacity设置为2的几次方 54 int cap = 1;// 每一个Segment中的HashEntry[]的初始化容量 55 while (cap < c) 56 cap <<= 1;// 创建容量 57 58 for (int i = 0; i < this.segments.length; ++i) 59 // 这一块this.segments.length就是ssize,为了不去计算这个值,可以直接改成i<ssize 60 this.segments[i] = new Segment<K, V>(cap, loadFactor); 61 }
注意:这个方法里边我在头部所写的注释非常重要,在这块注释写明了:
ConcurrentHashMap()
/** * 创建ConcurrentHashMap */ public ConcurrentHashMap() { this(DEFAULT_INITIAL_CAPACITY, // 16 DEFAULT_LOAD_FACTOR, // 0.75f DEFAULT_CONCURRENCY_LEVEL);// 16 }
该方法调用了上边的三参构造器。
五点注意:
两点改进:
在Concurrenthashmap的构造过程中,相对于JDK的代码,有两点改进:
for (int i = 0; i < this.segments.length; ++i) // 这一块this.segments.length就是ssize,为了不去计算这个值,可以直接改成i<ssize this.segments[i] = new Segment<K, V>(cap, loadFactor);
/** * 这里要注意一个很不好的编程习惯,就是小写l,容易与数字1混淆,所以最好不要用小写l,可以改为大写L */ Segment(int initialCapacity, float lf) { loadFactor = lf;//每个Segment的加载因子 setTable(HashEntry.<K, V> newArray(initialCapacity)); }
一个疑问:
3、put(Object key, Object value)
上述方法,若添加已有key的key-value对,则新值覆盖旧值。
putIfAbsent(K key, V value):若添加已有key的key-value对,直接返回旧值,则新值相当于没有添加。
使用方法:
map.put("hello", "world");
源代码:
ConcurrentHashMap的put(Object key, Object value)方法
/** * 将key-value放入map * 注意:key和value都不可以为空 * 步骤: * 1)计算key.hashCode()的hash值 * 2)根据hash值定位到某个Segment * 3)调用Segment的put()方法 * Segment的put()方法: * 1)上锁 * 2)从主内存中读取key-value对个数count * 3)count+1如果大于threshold,执行rehash() * 4)计算将要插入的HashEntry[]的下标index * 5)获取HashEntry的头节点HashEntry[index]-->first * 6)从头结点开始遍历整个HashEntry链表, * 6.1)若找到与key和hash相同的节点,则判断onlyIfAbsent如果为false,新值覆盖旧值,返回旧值;如果为true,则直接返回旧值(相当于不添加重复key的元素) * 6.2)若没有找到与key和hash相同的节点,则创建新节点HashEntry,并将之前的有节点作为新节点的next,即将新节点放入链头,然后将新节点赋值给HashEntry[index],将count强制写入主内存,最后返回null */ public V put(K key, V value) { if (key == null || value == null) throw new NullPointerException(); int hash = hash(key.hashCode());//计算key.hashCode()的hash值 /** * 根据hash值定位到某个Segment,调用Segment的put()方法 */ return segmentFor(hash).put(key, hash, value, false); }
注意:
if (key == null || value == null) throw new NullPointerException();
Segment的put(K key, int hash, V value, boolean onlyIfAbsent)
/** * 往当前segment中添加key-value * 注意: * 1)onlyIfAbsent-->false如果有旧值存在,新值覆盖旧值,返回旧值;true如果有旧值存在,则直接返回旧值,相当于不添加元素(不可添加重复key的元素) * 2)ReentrantLock的用法 * 3)volatile只能配合锁去使用才能实现原子性 */ V put(K key, int hash, V value, boolean onlyIfAbsent) { lock();//加锁:ReentrantLock try { int c = count;//当前Segment中的key-value对(注意:由于count是volatile型的,所以读的时候工作内存会从主内存重新加载count值) if (c++ > threshold) // 需要扩容 rehash();//扩容 HashEntry<K, V>[] tab = table; int index = hash & (tab.length - 1);//按位与获取数组下标:与HashMap相同 HashEntry<K, V> first = tab[index];//获取相应的HashEntry[i]中的头节点 HashEntry<K, V> e = first; //一直遍历到与插入节点的hash和key相同的节点e;若没有,最后e==null while (e != null && (e.hash != hash || !key.equals(e.key))) e = e.next; V oldValue;//旧值 if (e != null) {//table中已经有与将要插入节点相同hash和key的节点 oldValue = e.value;//获取旧值 if (!onlyIfAbsent) e.value = value;//false 覆盖旧值 true的话,就不添加元素了 } else {//table中没有与将要插入节点相同hash或key的节点 oldValue = null; ++modCount; tab[index] = new HashEntry<K, V>(key, hash, first, value);//将头节点作为新节点的next,所以新加入的元素也是添加在链头 count = c; //设置key-value对(注意:由于count是volatile型的,所以写的时候工作内存会立即向主内存重新写入count值) } return oldValue; } finally { unlock();//手工释放锁 } }
注意:在注释中已经写明了,这里还是要写一下
补:volatile的介绍见《附2 volatile》,链接如下:
http://www.cnblogs.com/java-zhao/p/5125698.html
hash(int h)
/** * 对key.hashCode()进行hash计算 * @param h key.hashCode() */ private static int hash(int h) { // Spread bits to regularize both segment and index locations, // using variant of single-word Wang/Jenkins hash. h += (h << 15) ^ 0xffffcd7d; h ^= (h >>> 10); h += (h << 3); h ^= (h >>> 6); h += (h << 2) + (h << 14); return h ^ (h >>> 16); }
segmentFor(int hash)
/** * 根据给定的key的hash值定位到一个Segment * @param hash */ final Segment<K, V> segmentFor(int hash) { return segments[(hash >>> segmentShift) & segmentMask]; }
注意:hash(int h)与segmentFor(int hash)这两个方法应该会尽量将key的hash值打散,从而保证尽可能多的同时在多个Segment上进行put操作,而不是在同一个Segment上执行多个put操作,这样之后,在同一个Segment中,要尽可能的保证向HashEntry[]的不同元素上进行put,而不是向同一个元素上一直put,以上两个函数究竟是怎样保证实现这样的将hash打散的效果呢?求大神指点啊!!!
rehash()
JDK的实现代码:
/** * 步骤: * 需要注意的是:同一个桶下边的HashEntry链表中的每一个元素的hash值不一定相同,只是hash&(table.length-1)的结果相同 * 1)创建一个新的HashEntry数组,容量为旧数组的二倍 * 2)计算新的threshold * 3)遍历旧数组的每一个元素,对于每一个元素 * 3.1)根据头节点e重新计算将要存入的新数组的索引idx * 3.2)若整个链表只有一个节点e,则是直接将e赋给newTable[idx]即可 * 3.3)若整个链表还有其他节点,先算出最后一个节点lastRun的位置lastIdx,并将最后一个节点赋值给newTable[lastIdx] * 3.4)最后将从头节点开始到最后一个节点之前的所有节点计算其将要存储的索引k,然后创建新节点,将新节点赋给newTable[k],并将之前newTable[k]上存在的节点作为新节点的下一节点 */ void rehash() { HashEntry<K, V>[] oldTable = table; int oldCapacity = oldTable.length; if (oldCapacity >= MAXIMUM_CAPACITY) return; HashEntry<K, V>[] newTable = HashEntry.newArray(oldCapacity << 1);//扩容为原来二倍 threshold = (int) (newTable.length * loadFactor);//计算新的扩容临界值 int sizeMask = newTable.length - 1; for (int i = 0; i < oldCapacity; i++) { // We need to guarantee that any existing reads of old Map can // proceed. So we cannot yet null out each bin. HashEntry<K, V> e = oldTable[i];//头节点 if (e != null) { HashEntry<K, V> next = e.next; int idx = e.hash & sizeMask;//重新按位与计算将要存放的新数组中的索引 if (next == null)//如果是只有一个头节点,只需将头节点设置到newTable[idx]即可 newTable[idx] = e; else { // Reuse trailing consecutive sequence at same slot HashEntry<K, V> lastRun = e; int lastIdx = idx;//存放最后一个元素将要存储的数组索引 //查找到最后一个元素,并设置相关信息 for (HashEntry<K, V> last = next; last != null; last = last.next) { int k = last.hash & sizeMask; if (k != lastIdx) { lastIdx = k; lastRun = last; } } newTable[lastIdx] = lastRun;//存放最后一个元素 // Clone all remaining nodes for (HashEntry<K, V> p = e; p != lastRun; p = p.next) { int k = p.hash & sizeMask; HashEntry<K, V> n = newTable[k];//获取newTable[k]已经存在的HashEntry,并将此HashEntry赋给n //创建新节点,并将之前的n作为新节点的下一节点 newTable[k] = new HashEntry<K, V>(p.key, p.hash, n,p.value); } } } } table = newTable; }
个人感觉JDK的实现方式比较拖沓,改造后的代码如下,如有问题,请指出!!!
我对其进行改造后的实现代码:
/** * 步骤: * 需要注意的是:同一个桶下边的HashEntry链表中的每一个元素的hash值不一定相同,只是hash&(table.length-1)的结果相同 * 1)创建一个新的HashEntry数组,容量为旧数组的二倍 * 2)计算新的threshold * 3)遍历旧数组的每一个元素,对于每一个元素(即一个链表) * 3.1)获取头节点e * 3.2)从头节点开始到最后一个节点(null之前的那个节点)的所有节点计算其将要存储的索引k,然后创建新节点,将新节点赋给newTable[k],并将之前newTable[k]上存在的节点作为新节点的下一节点 */ void rehash() { HashEntry<K, V>[] oldTable = table; int oldCapacity = oldTable.length; if (oldCapacity >= MAXIMUM_CAPACITY) return; HashEntry<K, V>[] newTable = HashEntry.newArray(oldCapacity << 1);//扩容为原来二倍 threshold = (int) (newTable.length * loadFactor);//计算新的扩容临界值 int sizeMask = newTable.length - 1; for (int i = 0; i < oldCapacity; i++) {//遍历每一个数组元素 // We need to guarantee that any existing reads of old Map can // proceed. So we cannot yet null out each bin. HashEntry<K, V> e = oldTable[i];//头节点 if (e != null) { for (HashEntry<K, V> p = e; p != null; p = p.next) {//遍历数组元素中的链表 int k = p.hash & sizeMask; HashEntry<K, V> n = newTable[k];//获取newTable[k]已经存在的HashEntry,并将此HashEntry赋给n //创建新节点,并将之前的n作为新节点的下一节点 newTable[k] = new HashEntry<K, V>(p.key, p.hash, n,p.value); } } } table = newTable; }
注意点:
总结:ConcurrentHashMap基于concurrencyLevel划分出多个Segment来存储key-value,这样的话put的时候只锁住当前的Segment,可以避免put的时候锁住整个map,从而减少了并发时的阻塞现象。
4、get(Object key)
使用方法:
map.get("hello");
源代码:
ConcurrentHashMap的get(Object key)
/** * 根据key获取value * 步骤: * 1)根据key获取hash值 * 2)根据hash值找到相应的Segment * 调用Segment的get(Object key, int hash) * 3)根据hash值找出HashEntry数组中的索引index,并返回HashEntry[index] * 4)遍历整个HashEntry[index]链表,找出hash和key与给定参数相等的HashEntry,例如e, * 4.1)如没找到e,返回null * 4.2)如找到e,获取e.value * 4.2.1)如果e.value!=null,直接返回 * 4.2.2)如果e.value==null,则先加锁,等并发的put操作将value设置成功后,再返回value值 */ public V get(Object key) { int hash = hash(key.hashCode()); return segmentFor(hash).get(key, hash); }
Segment的get(Object key, int hash)
/** * 根据key和hash值获取value */ V get(Object key, int hash) { if (count != 0) { // read-volatile HashEntry<K, V> e = getFirst(hash);//找到HashEntry[index] while (e != null) {//遍历整个链表 if (e.hash == hash && key.equals(e.key)) { V v = e.value; if (v != null) return v; /* * 如果V等于null,有可能是当下的这个HashEntry刚刚被创建,value属性还没有设置成功, * 这时候我们读到是该HashEntry的value的默认值null,所以这里加锁,等待put结束后,返回value值 */ return readValueUnderLock(e); } e = e.next; } } return null; }
Segment的getFirst(int hash)
/** * 根据hash值找出HashEntry数组中的索引index,并返回HashEntry[index] */ HashEntry<K, V> getFirst(int hash) { HashEntry<K, V>[] tab = table; return tab[hash & (tab.length - 1)]; }
Segment的readValueUnderLock(HashEntry<K, V> e)
V readValueUnderLock(HashEntry<K, V> e) { lock(); try { return e.value; } finally { unlock(); } }
注意点:
5、remove(Object key)
使用方法:
map.remove("hello");
源代码:
ConcurrentHashMap的remove(Object key)
/** * 删除指定key的元素 * 步骤: * 1)根据key获取hash值 * 2)根据hash值获取Segment * 调用Segment的remove(Object key, int hash, Object value) * 1)count-1 * 2)获取将要删除的元素所在的HashEntry[index] * 3)遍历链表, * 3.1)若没有hash和key都与指定参数相同的节点e,返回null * 3.2)若有e,删除指定节点e,并将e之前的节点重新排序后,将排序后的最后一个节点的下一个节点指定为e的下一个节点 * (很绕,不知道JDK为什么这样实现) */ public V remove(Object key) { int hash = hash(key.hashCode()); return segmentFor(hash).remove(key, hash, null); }
Segment的remove(Object key, int hash, Object value)
V remove(Object key, int hash, Object value) { lock(); try { int c = count - 1;//key-value对个数-1 HashEntry<K, V>[] tab = table; int index = hash & (tab.length - 1); HashEntry<K, V> first = tab[index];//获取将要删除的元素所在的HashEntry[index] HashEntry<K, V> e = first; //从头节点遍历到最后,若未找到相关的HashEntry,e==null,否则,有 while (e != null && (e.hash != hash || !key.equals(e.key))) e = e.next; V oldValue = null; if (e != null) {//将要删除的节点e V v = e.value; if (value == null || value.equals(v)) { oldValue = v; // All entries following removed node can stay // in list, but all preceding ones need to be // cloned. ++modCount; HashEntry<K, V> newFirst = e.next; /* * 从头结点遍历到e节点,这里将e节点删除了,但是删除节点e的前边的节点会倒序 * eg.原本的顺序:E3-->E2-->E1-->E0,删除E1节点后的顺序为:E2-->E3-->E0 * E1前的节点倒序排列了 */ 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(); } }
注意:具体的实现方式看注释,个人感觉比较绕,所以有兴趣的朋友可以按照如下步骤实现了一遍:(实现的过程可以参照HashMap的remove(Object key))
6、containsKey(Object key)
使用方法:
map.containsKey("hello")
源代码:
ConcurrentHashMap的containsKey(Object key)
/** * 是否包含指定key的数据 * 步骤: * 1)根据key计算hash值 * 2)根据hash获取相应的Segment * 调用Segment的containsKey(Object key, int hash) * 3)根据hash值找出HashEntry数组中的索引index,并返回HashEntry[index] * 4)遍历整个HashEntry[index]链表,找出hash和key与给定参数相等的HashEntry,例如e, * 4.1)如找到e,返回true * 4.2)如没找到e,返回false */ public boolean containsKey(Object key) { int hash = hash(key.hashCode()); return segmentFor(hash).containsKey(key, hash); }
Segment的containsKey(Object key, int hash)
boolean containsKey(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)) return true; e = e.next; } } return false; }
说明:代码清晰简单,流程步骤查看注释即可
7、keySet().iterator()
使用方法:
Map<String, Object> map = new ConcurrentHashMap<String, Object>(); map.put("hello3", "world2"); map.put("hello2", "world"); for(String key : map.keySet()){ System.out.println(map.get(key)); }
源代码不写了。
流程:
遍历每个Segment中的HashEntry[],完成所有对象的读取,不加锁。
8、size()
源代码:
/** * 计算map中的key-value对总数 * 步骤: * 1)遍历所有段,计算总的count值sum,计算总的modCount值 * 2)如果有数据的话(modCount!=0),再遍历所有段一遍,计算总的count值check,在这期间只要有一个段的modCount发生了变化,就再重复如上动作两次 * 3)若三次后,还未成功,遍历所有Segment,分别加锁(即建立全局锁),然后计算,最后释放所有锁 */ public int size() { final Segment<K, V>[] segments = this.segments; long sum = 0;//总量 long check = 0;//标志位 int[] mc = new int[segments.length];//存放每个段的modCount for (int k = 0; k < RETRIES_BEFORE_LOCK; ++k) { check = 0; sum = 0;//总的count值 int mcsum = 0;//总的modCount值 for (int i = 0; i < segments.length; ++i) {//遍历所有段 sum += segments[i].count;//计算总的count值 mcsum += mc[i] = segments[i].modCount;//计算总的modCount值 } if (mcsum != 0) {//有数据的话,再检查一遍 for (int i = 0; i < segments.length; ++i) { check += segments[i].count;//计算总的count if (mc[i] != segments[i].modCount) {//只要有一个段发生了变化(在遍历期间发生了增删变化) check = -1; break;//跳出所有循环 } } } if (check == sum)//成功 break; } if (check != sum) { //以上三次都为成功的话 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; }
在不加锁的情况下遍历所有Segment,读取每个Segment的count和modCount,并进行统计;
完毕后,再遍历一遍所有Segment,比较modCount,是否发生了变化,若发生了变化,则再重复如上动作两次;
若三次后,还未成功,遍历所有Segment,分别加锁(即建立全局锁),然后计算,最后释放所有锁。
注:以如上的方式,大部分情况下,不需要加锁就可以获取size()
总结: