一、概述

  本章使用的是JDK8。

  阅读本章请先了解HashMap的实现原理【Java】HashMap 的实现原理

1.1 ConcurrentHashMap跟HashMap,HashTable的对比

1. HashMap不是线程安全:

  在并发环境下,可能会形成环状链表(扩容时可能造成,具体原因自行百度google或查看源码分析),导致get操作时,cpu空转,所以,在并发环境中使用HashMap是非常危险的,实现原理参考:【Java】HashMap 的实现原理

2. HashTable是线程安全的:

  HashTable和HashMap的实现原理几乎一样,

  差别:

  • 1.HashTable不允许key和value为null;
  • 2.HashTable是线程安全的。

  HashTable线程安全的策略实现代价却比较大,get/put所有相关操作都是synchronized的,这相当于给整个哈希表加了一把大锁,多线程访问时候,只要有一个线程访问或操作该对象,那其他线程只能阻塞

3. ConcurrentHashMap是线程安全的:

  直接采用transient volatile HashEntry<K,V>[] table保存数据,采用table数组元素作为锁,从而实现了对每一行数据进行加锁,并发控制使用Synchronized和CAS来操作,这样在多线程访问时不同段的数据时,就不会存在锁竞争了,这 样便可以有效地提高并发效率。这就是ConcurrentHashMap所采用的"分段锁"思想

二、结构图

  【Java多线程】ConcurrentHashMap 的实现原理(二十)

  由图可以看出,ConcurrentHashMap的结构是:数组 + 链表 + 红黑树

三、属性

 1 // node数组最大容量:2^30=1073741824
 2 private static final int MAXIMUM_CAPACITY = 1 << 30;
 3 
 4 // 默认初始值,必须是2的幕数
 5 private static final int DEFAULT_CAPACITY = 16;
 6 
 7 //数组可能最大值,需要与toArray()相关方法关联
 8 static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
 9 
10 //并发级别,遗留下来的,为兼容以前的版本
11 private static final int DEFAULT_CONCURRENCY_LEVEL = 16;
12 
13 // 负载因子
14 private static final float LOAD_FACTOR = 0.75f;
15 
16 // 链表转红黑树阀值,> 8 链表转换为红黑树
17 static final int TREEIFY_THRESHOLD = 8;
18 
19 // 链表转红黑树时,最小容量阀值, >= 64 链表转换为红黑树
20 static final int MIN_TREEIFY_CAPACITY = 64;
21 
22 // 树转链表阀值,小于等于6(tranfer时,lc、hc=0两个计数器分别++记录原bin、新binTreeNode数量,<=UNTREEIFY_THRESHOLD 则untreeify(lo))
23 static final int UNTREEIFY_THRESHOLD = 6;
24 
25 // 最小转移数组步长
26 private static final int MIN_TRANSFER_STRIDE = 16;
27 
28 // 可以帮助调整大小的最大线程数,计算用到
29 private static int RESIZE_STAMP_BITS = 16;
30 
31 // 2^15-1,help resize的最大线程数
32 private static final int MAX_RESIZERS = (1 << (32 - RESIZE_STAMP_BITS)) - 1;
33 
34 // 32-16=16,sizeCtl中记录size大小的偏移量
35 private static final int RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS;
36 
37 // forwarding nodes的hash值
38 static final int MOVED     = -1;
39 // 树根节点的hash值
40 static final int TREEBIN   = -2;
41 // ReservationNode的hash值
42 static final int RESERVED  = -3;
43 
44 // 可用处理器数量
45 static final int NCPU = Runtime.getRuntime().availableProcessors();
46 
47 // 存放node的数组
48 transient volatile Node<K,V>[] table;
49 
50 // 控制标识符,用来控制table的初始化和扩容的操作,不同的值有不同的含义
51 // 当为0时:代表当时的table还没有被初始化
52 // 当为负数时:-1代表正在初始化
53 // 小于-1时:实际值为resizeStamp(n) <<RESIZE_STAMP_SHIFT+2, 表明table正在扩容
54 // 当为正数时:未初始化,代表初始化大小,初始化完成后, 代表下一次扩容时的阀值, 默认为0.75*n
55 private transient volatile int sizeCtl;
56 
57 // table容量从n扩到2n时, 是从索引n->1的元素开始迁移, transferIndex代表当前已经迁移的元素下标
58 private transient volatile int transferIndex;
59 
60 // 基础计算器值
61 private transient volatile long baseCount;
62 
63 // 计数器数组
64 private transient volatile CounterCell[] counterCells;

 

  下面是 Node 类的定义,它是 HashMap 中的一个静态内部类,哈希表中的每一个 节点都是 Node 类型。我们可以看到,Node 类中有 4 个属性,其中除了 key 和 value 之外,还有 hash 和 next 两个属性。hash 是用来存储 key 的哈希值的,next 是在构建链表时用来指向后继节点的

 1 static class Node<K,V> implements Map.Entry<K,V> {
 2     final int hash;
 3     final K key;
 4     volatile V val;
 5     volatile Node<K,V> next;
 6 
 7     Node(int hash, K key, V val, Node<K,V> next) {
 8         this.hash = hash;
 9         this.key = key;
10         this.val = val;
11         this.next = next;
12     }
13 
14     ......
15 }

  树节点TreeNode

 1 static final class TreeNode<K,V> extends Node<K,V> {
 2     TreeNode<K,V> parent;  // red-black tree links
 3     TreeNode<K,V> left;
 4     TreeNode<K,V> right;
 5     TreeNode<K,V> prev;    // needed to unlink next upon deletion
 6     boolean red;
 7 
 8 
 9     ......
10 }
View Code

相关文章:

  • 2021-12-14
  • 2022-02-03
  • 2022-01-03
  • 2021-05-31
  • 2022-01-06
  • 2021-12-27
猜你喜欢
  • 2022-02-06
  • 2021-12-22
  • 2021-06-07
  • 2021-08-09
  • 2021-08-10
相关资源
相似解决方案