[关闭]
@yudesong 2018-02-12T03:03:17.000000Z 字数 7846 阅读 783

HashMap、ConcurrentHashMap、HashTable、CopyOnWriteArrayList源码分析

HashMap


HashMap

首先看看HashMap的结构图,采用了数组+链表的方式。
屏幕快照 2018-01-29 21.28.36.png

实现原理
屏幕快照 2018-01-29 21.40.44.png

  1. public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>,
  2. Cloneable, Serializable {
  3. //实际存储的key-value键值对的个数
  4. transient int size;
  5. //阈值,当table == {}时,该值为初始容量(初始容量默认为16);当table被填充了,也就是为table分配内存空间后,threshold一般为 capacity*loadFactory。HashMap在进行扩容时需要参考threshold,后面会详细谈到
  6. int threshold;
  7. //负载因子,代表了table的填充度有多少,默认是0.75
  8. final float loadFactor;
  9. //用于快速失败,由于HashMap非线程安全,在对HashMap进行迭代时,如果期间其他线程的参与导致HashMap的结构发生变化了(比如put,remove等操作),需要抛出异常ConcurrentModificationException
  10. transient int modCount;
  11. //HashMap的主干数组,可以看到就是一个Entry数组,初始值为空数组{},主干数组的长度一定是2的次幂,至于为什么这么做,后面会有详细分析。
  12. transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
  13. public HashMap(int initialCapacity, float loadFactor) {
  14.  //此处对传入的初始容量进行校验,最大不能超过MAXIMUM_CAPACITY = 1<<30(230)
  15. if (initialCapacity < 0)
  16. throw new IllegalArgumentException("Illegal initial capacity: " +
  17. initialCapacity);
  18. if (initialCapacity > MAXIMUM_CAPACITY)
  19. initialCapacity = MAXIMUM_CAPACITY;
  20. if (loadFactor <= 0 || Float.isNaN(loadFactor))
  21. throw new IllegalArgumentException("Illegal load factor: " +
  22. loadFactor);
  23. this.loadFactor = loadFactor;
  24. threshold = initialCapacity;
  25.      
  26. init();//init方法在HashMap中没有实际实现,不过在其子类如 linkedHashMap中就会有对应实现
  27. }
  28. public V put(K key, V value) {
  29. //如果table数组为空数组{},进行数组填充(为table分配实际内存空间),入参为threshold,此时threshold为initialCapacity 默认是1<<4(24=16)
  30. if (table == EMPTY_TABLE) {
  31. inflateTable(threshold);
  32. }
  33. //如果key为null,存储位置为table[0]或table[0]的冲突链上
  34. if (key == null)
  35. return putForNullKey(value);
  36. int hash = hash(key);//对key的hashcode进一步计算,确保散列均匀
  37. int i = indexFor(hash, table.length);//获取在table中的实际位置
  38. for (Entry<K,V> e = table[i]; e != null; e = e.next) {
  39. //如果该对应数据已存在,执行覆盖操作。用新value替换旧value,并返回旧value
  40. Object k;
  41. if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
  42. V oldValue = e.value;
  43. e.value = value;
  44. e.recordAccess(this);
  45. return oldValue;
  46. }
  47. }
  48. modCount++;//保证并发访问时,若HashMap内部结构发生变化,快速响应失败
  49. addEntry(hash, key, value, i);//新增一个entry
  50. return null;
  51. }
  52. public V get(Object key) {
  53.      //如果key为null,则直接去table[0]处去检索即可。
  54. if (key == null)
  55. return getForNullKey();
  56. Entry<K,V> entry = getEntry(key);
  57. return null == entry ? null : entry.getValue();
  58. }
  59. final Entry<K,V> getEntry(Object key) {
  60. if (size == 0) {
  61. return null;
  62. }
  63. //通过key的hashcode值计算hash值
  64. int hash = (key == null) ? 0 : hash(key);
  65. //indexFor (hash&length-1) 获取最终数组索引,然后遍历链表,通过equals方法比对找出对应记录
  66. for (Entry<K,V> e = table[indexFor(hash, table.length)];
  67. e != null;
  68. e = e.next) {
  69. Object k;
  70. if (e.hash == hash &&
  71. ((k = e.key) == key || (key != null && key.equals(k))))
  72. return e;
  73. }
  74. return null;
  75. }
  76. static class Entry<K,V> implements Map.Entry<K,V> {
  77. final K key;
  78. V value;
  79. Entry<K,V> next;//存储指向下一个Entry的引用,单链表结构
  80. int hash;//对key的hashcode值进行hash运算后得到的值,存储在Entry,避免重复计算
  81. /**
  82. * Creates new entry.
  83. */
  84. Entry(int h, K k, V v, Entry<K,V> n) {
  85. value = v;
  86. next = n;
  87. key = k;
  88. hash = h;
  89. }
  90. }

ConcurrentHashMap

屏幕快照 2018-01-29 22.00.36.png

  1. public ConcurrentHashMap(int initialCapacity,
  2. float loadFactor, int concurrencyLevel) {
  3. if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0)
  4. throw new IllegalArgumentException();
  5. //MAX_SEGMENTS 为1<<16=65536,也就是最大并发数为65536
  6. if (concurrencyLevel > MAX_SEGMENTS)
  7. concurrencyLevel = MAX_SEGMENTS;
  8. //2的sshif次方等于ssize,例:ssize=16,sshift=4;ssize=32,sshif=5
  9. int sshift = 0;
  10. //ssize 为segments数组长度,根据concurrentLevel计算得出
  11. int ssize = 1;
  12. while (ssize < concurrencyLevel) {
  13. ++sshift;
  14. ssize <<= 1;
  15. }
  16. //segmentShift和segmentMask这两个变量在定位segment时会用到,后面会详细讲
  17. this.segmentShift = 32 - sshift;
  18. this.segmentMask = ssize - 1;
  19. if (initialCapacity > MAXIMUM_CAPACITY)
  20. initialCapacity = MAXIMUM_CAPACITY;
  21. //计算cap的大小,即Segment中HashEntry的数组长度,cap也一定为2的n次方.
  22. int c = initialCapacity / ssize;
  23. if (c * ssize < initialCapacity)
  24. ++c;
  25. int cap = MIN_SEGMENT_TABLE_CAPACITY;
  26. while (cap < c)
  27. cap <<= 1;
  28. //创建segments数组并初始化第一个Segment,其余的Segment延迟初始化
  29. Segment<K,V> s0 =
  30. new Segment<K,V>(loadFactor, (int)(cap * loadFactor),
  31. (HashEntry<K,V>[])new HashEntry[cap]);
  32. Segment<K,V>[] ss = (Segment<K,V>[])new Segment[ssize];
  33. UNSAFE.putOrderedObject(ss, SBASE, s0);
  34. this.segments = ss;
  35. }
  36. public V put(K key, V value) {
  37. Segment<K,V> s;
  38. //concurrentHashMap不允许key/value为空
  39. if (value == null)
  40. throw new NullPointerException();
  41. //hash函数对key的hashCode重新散列,避免差劲的不合理的hashcode,保证散列均匀
  42. int hash = hash(key);
  43. //返回的hash值无符号右移segmentShift位与段掩码进行位运算,定位segment
  44. int j = (hash >>> segmentShift) & segmentMask;
  45. if ((s = (Segment<K,V>)UNSAFE.getObject // nonvolatile; recheck
  46. (segments, (j << SSHIFT) + SBASE)) == null) // in ensureSegment
  47. s = ensureSegment(j);
  48. return s.put(key, hash, value, false);
  49. }
  50. public V get(Object key) {
  51. Segment<K,V> s;
  52. HashEntry<K,V>[] tab;
  53. int h = hash(key);
  54. long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE;
  55. //先定位Segment,再定位HashEntry
  56. if ((s = (Segment<K,V>)UNSAFE.getObjectVolatile(segments, u)) != null &&
  57. (tab = s.table) != null) {
  58. for (HashEntry<K,V> e = (HashEntry<K,V>) UNSAFE.getObjectVolatile
  59. (tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE);
  60. e != null; e = e.next) {
  61. K k;
  62. if ((k = e.key) == key || (e.hash == h && key.equals(k)))
  63. return e.value;
  64. }
  65. }
  66. return null;
  67. }
  68. final V put(K key, int hash, V value, boolean onlyIfAbsent) {
  69. HashEntry<K,V> node = tryLock() ? null :
  70. scanAndLockForPut(key, hash, value);//tryLock不成功时会遍历定位到的HashEnry位置的链表(遍历主要是为了使CPU缓存链表),若找不到,则创建HashEntry。tryLock一定次数后(MAX_SCAN_RETRIES变量决定),则lock。若遍历过程中,由于其他线程的操作导致链表头结点变化,则需要重新遍历。
  71. V oldValue;
  72. try {
  73. HashEntry<K,V>[] tab = table;
  74. int index = (tab.length - 1) & hash;//定位HashEntry,可以看到,这个hash值在定位Segment时和在Segment中定位HashEntry都会用到,只不过定位Segment时只用到高几位。
  75. HashEntry<K,V> first = entryAt(tab, index);
  76. for (HashEntry<K,V> e = first;;) {
  77. if (e != null) {
  78. K k;
  79. if ((k = e.key) == key ||
  80. (e.hash == hash && key.equals(k))) {
  81. oldValue = e.value;
  82. if (!onlyIfAbsent) {
  83. e.value = value;
  84. ++modCount;
  85. }
  86. break;
  87. }
  88. e = e.next;
  89. }
  90. else {
  91. if (node != null)
  92. node.setNext(first);
  93. else
  94. node = new HashEntry<K,V>(hash, key, value, first);
  95. int c = count + 1;
  96.               //若c超出阈值threshold,需要扩容并rehash。扩容后的容量是当前容量的2倍。这样可以最大程度避免之前散列好的entry重新散列,具体在另一篇文章中有详细分析,不赘述。扩容并rehash的这个过程是比较消耗资源的。
  97. if (c > threshold && tab.length < MAXIMUM_CAPACITY)
  98. rehash(node);
  99. else
  100. setEntryAt(tab, index, node);
  101. ++modCount;
  102. count = c;
  103. oldValue = null;
  104. break;
  105. }
  106. }
  107. } finally {
  108. unlock();
  109. }
  110. return oldValue;
  111. }

CopyOnWriteArrayList

CopyOnWriteArrayList是Java并发包中提供的一个并发容器,它是个线程安全且读操作无锁的ArrayList,写操作则通过创建底层数组的新副本来实现,是一种读写分离的并发策略,我们也可以称这种容器为"写时复制器",Java并发包中类似的容器还有CopyOnWriteSet。

实现原理
我们都知道,集合框架中的ArrayList是非线程安全的,Vector虽是线程安全的,但由于简单粗暴的锁同步机制,性能较差。而CopyOnWriteArrayList则提供了另一种不同的并发处理策略。很多时候,我们的系统应对的都是读多写少的并发场景。CopyOnWriteArrayList容器允许并发读,读操作是无锁的,性能较高。至于写操作,比如向容器中添加一个元素,则首先将当前容器复制一份,然后在新副本上执行写操作,结束之后再将原容器的引用指向新容器。

优缺点分析
了解了CopyOnWriteArrayList的实现原理,分析它的优缺点及使用场景就很容易了。

  1. public boolean add(E e) {
  2. //ReentrantLock加锁,保证线程安全
  3. final ReentrantLock lock = this.lock;
  4. lock.lock();
  5. try {
  6. Object[] elements = getArray();
  7. int len = elements.length;
  8. //拷贝原容器,长度为原容器长度加一
  9. Object[] newElements = Arrays.copyOf(elements, len + 1);
  10. //在新副本上执行添加操作
  11. newElements[len] = e;
  12. //将原容器引用指向新副本
  13. setArray(newElements);
  14. return true;
  15. } finally {
  16. //解锁
  17. lock.unlock();
  18. }
  19. }
  20. public E remove(int index) {
  21. //加锁
  22. final ReentrantLock lock = this.lock;
  23. lock.lock();
  24. try {
  25. Object[] elements = getArray();
  26. int len = elements.length;
  27. E oldValue = get(elements, index);
  28. int numMoved = len - index - 1;
  29. if (numMoved == 0)
  30. //如果要删除的是列表末端数据,拷贝前len-1个数据到新副本上,再切换引用
  31. setArray(Arrays.copyOf(elements, len - 1));
  32. else {
  33. //否则,将除要删除元素之外的其他元素拷贝到新副本中,并切换引用
  34. Object[] newElements = new Object[len - 1];
  35. System.arraycopy(elements, 0, newElements, 0, index);
  36. System.arraycopy(elements, index + 1, newElements, index,
  37. numMoved);
  38. setArray(newElements);
  39. }
  40. return oldValue;
  41. } finally {
  42. //解锁
  43. lock.unlock();
  44. }
  45. }
  46. public E get(int index) {
  47. return get(getArray(), index);
  48. }
  49. private E get(Object[] a, int index) {
  50. return (E) a[index];
  51. }

参考博客
1.HashMap实现原理及源码分析
2.CopyOnWriteArrayList实现原理及源码分析
3. 造成HashMap非线程安全的原因

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注