ConcurrentHashMap 解读

 


 

初始化:

问题:如何当且仅只有一个线程初始化table

 1 private final Node<K,V>[] initTable() {
 2         Node<K,V>[] tab; int sc;
 3         while ((tab = table) == null || tab.length == 0) {
 4             if ((sc = sizeCtl) < 0)
 5                 Thread.yield(); // lost initialization race; just spin
 6             else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
 7                 try {
 8                     if ((tab = table) == null || tab.length == 0) {
 9                         int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
10                         @SuppressWarnings("unchecked")
11                         Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
12                         table = tab = nt;
13                         sc = n - (n >>> 2);
14                     }
15                 } finally {
16                     sizeCtl = sc;
17                 }
18                 break;
19             }
20         }
21         return tab;
22     }
    transient volatile Node<K,V>[] table;private transient volatile int sizeCtl;

1、第3行  判断当前系统的table是否为空,这里用volatile 修饰table,对于各个线程都是可见的

2、第4行 判断sizeCtl 是否小于零,因为在初始化的过程中,会把sizeCtl设置成-1,所以如果小于零,说明当前有其他线程正在进行初始化,所以直接让出cpu时间

3、第6行 如果上一步的判断不小于零,那么这一步就要把sizeCtl设置成-1,这里用Unsafe类保证了仅只有一个线程能修改sizeCtl的值从0到-1

4、9-13行 初始化table

5、第16行 将sizeCtl 值设置为12

 


 

取值

 1 public V get(Object key) {
 2         Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
 3         int h = spread(key.hashCode());
 4         if ((tab = table) != null && (n = tab.length) > 0 &&
 5             (e = tabAt(tab, (n - 1) & h)) != null) {
 6             if ((eh = e.hash) == h) {
 7                 if ((ek = e.key) == key || (ek != null && key.equals(ek)))
 8                     return e.val;
 9             }
10             else if (eh < 0)
11                 return (p = e.find(h, key)) != null ? p.val : null;
12             while ((e = e.next) != null) {
13                 if (e.hash == h &&
14                     ((ek = e.key) == key || (ek != null && key.equals(ek))))
15                     return e.val;
16             }
17         }
18         return null;
19     }
static final int spread(int h) {return (h ^ (h >>> 16)) & HASH_BITS;}

 

1.  第2行 定义一堆变量下面用

2. 第3行调整hash值分布的方法,类似于HashMap中的hash(Object key)方法,通过位运算来使key分布更均匀

3. 第4-5行判断 table 是否有值,如果有值 根据第三行计算出来的结果 计算出key在tab中的Node位置,取得Node中的first node

4. 第6-9行 hash值比对判断 如果刚刚取得的first node的hash值和当前key的hash值相同,那么就开始获取值

5. 第10-11 如果刚刚取的node的hash值小于0,那么这个node是个红黑树,进入到红黑树里面查询

6.第12-17 如果以上条件都不对,那么就开始遍历查询

 


 

扩容:

1.扩容的时机

  1.链表新增节点后,所在链表的节点数会达到阈值,转变成红黑树,在转变之前会对数组长度做一次判断,如果数组的长度小于64,则会用调用tryPresize方法把数组长度扩大到原来的两倍,并触发transfer方法,重新调整节点的位置

static final int MIN_TREEIFY_CAPACITY = 64;

 

  2.新增节点之后,会调用addCount方法记录元素个数,并检查是否需要进行扩容,当数组元素个数达到阈值时,会触发transfer方法,重新调整节点的位置

2.扩容的执行:

  1 /**
  2     * 一个过渡的table表  只有在扩容的时候才会使用
  3     */
  4    private transient volatile Node<K,V>[] nextTable;
  5  
  6 /**
  7     * Moves and/or copies the nodes in each bin to new table. See
  8     * above for explanation.
  9     */
 10    private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
 11        int n = tab.length, stride;
 12        if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
 13            stride = MIN_TRANSFER_STRIDE; // subdivide range
 14        if (nextTab == null) {            // initiating
 15            try {
 16                @SuppressWarnings("unchecked")
 17                Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1];//构造一个nextTable对象 它的容量是原来的两倍
 18                nextTab = nt;
 19            } catch (Throwable ex) {      // try to cope with OOME
 20                sizeCtl = Integer.MAX_VALUE;
 21                return;
 22            }
 23            nextTable = nextTab;
 24            transferIndex = n;
 25        }
 26        int nextn = nextTab.length;
 27        ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);//构造一个连节点指针 用于标志位
 28        boolean advance = true;//并发扩容的关键属性 如果等于true 说明这个节点已经处理过
 29        boolean finishing = false; // to ensure sweep before committing nextTab
 30        for (int i = 0, bound = 0;;) {
 31            Node<K,V> f; int fh;
 32            //这个while循环体的作用就是在控制i--  通过i--可以依次遍历原hash表中的节点
 33            while (advance) {
 34                int nextIndex, nextBound;
 35                if (--i >= bound || finishing)
 36                    advance = false;
 37                else if ((nextIndex = transferIndex) <= 0) {
 38                    i = -1;
 39                    advance = false;
 40                }
 41                else if (U.compareAndSwapInt
 42                         (this, TRANSFERINDEX, nextIndex,
 43                          nextBound = (nextIndex > stride ?
 44                                       nextIndex - stride : 0))) {
 45                    bound = nextBound;
 46                    i = nextIndex - 1;
 47                    advance = false;
 48                }
 49            }
 50            if (i < 0 || i >= n || i + n >= nextn) {
 51                int sc;
 52                if (finishing) {
 53                    //如果所有的节点都已经完成复制工作  就把nextTable赋值给table 清空临时对象nextTable
 54                    nextTable = null;
 55                    table = nextTab;
 56                    sizeCtl = (n << 1) - (n >>> 1);//扩容阈值设置为原来容量的1.5倍  依然相当于现在容量的0.75倍
 57                    return;
 58                }
 59                //利用CAS方法更新这个扩容阈值,在这里面sizectl值减一,说明新加入一个线程参与到扩容操作
 60                if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {
 61                    if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)
 62                        return;
 63                    finishing = advance = true;
 64                    i = n; // recheck before commit
 65                }
 66            }
 67            //如果遍历到的节点为空 则放入ForwardingNode指针
 68            else if ((f = tabAt(tab, i)) == null)
 69                advance = casTabAt(tab, i, null, fwd);
 70            //如果遍历到ForwardingNode节点  说明这个点已经被处理过了 直接跳过  这里是控制并发扩容的核心
 71            else if ((fh = f.hash) == MOVED)
 72                advance = true; // already processed
 73            else {
 74                    //节点上锁
 75                synchronized (f) {
 76                    if (tabAt(tab, i) == f) {
 77                        Node<K,V> ln, hn;
 78                        //如果fh>=0 证明这是一个Node节点
 79                        if (fh >= 0) {
 80                            int runBit = fh & n;
 81                            //以下的部分在完成的工作是构造两个链表  一个是原链表  另一个是原链表的反序排列
 82                            Node<K,V> lastRun = f;
 83                            for (Node<K,V> p = f.next; p != null; p = p.next) {
 84                                int b = p.hash & n;
 85                                if (b != runBit) {
 86                                    runBit = b;
 87                                    lastRun = p;
 88                                }
 89                            }
 90                            if (runBit == 0) {
 91                                ln = lastRun;
 92                                hn = null;
 93                            }
 94                            else {
 95                                hn = lastRun;
 96                                ln = null;
 97                            }
 98                            for (Node<K,V> p = f; p != lastRun; p = p.next) {
 99                                int ph = p.hash; K pk = p.key; V pv = p.val;
100                                if ((ph & n) == 0)
101                                    ln = new Node<K,V>(ph, pk, pv, ln);
102                                else
103                                    hn = new Node<K,V>(ph, pk, pv, hn);
104                            }
105                            //在nextTable的i位置上插入一个链表
106                            setTabAt(nextTab, i, ln);
107                            //在nextTable的i+n的位置上插入另一个链表
108                            setTabAt(nextTab, i + n, hn);
109                            //在table的i位置上插入forwardNode节点  表示已经处理过该节点
110                            setTabAt(tab, i, fwd);
111                            //设置advance为true 返回到上面的while循环中 就可以执行i--操作
112                            advance = true;
113                        }
114                        //对TreeBin对象进行处理  与上面的过程类似
115                        else if (f instanceof TreeBin) {
116                            TreeBin<K,V> t = (TreeBin<K,V>)f;
117                            TreeNode<K,V> lo = null, loTail = null;
118                            TreeNode<K,V> hi = null, hiTail = null;
119                            int lc = 0, hc = 0;
120                            //构造正序和反序两个链表
121                            for (Node<K,V> e = t.first; e != null; e = e.next) {
122                                int h = e.hash;
123                                TreeNode<K,V> p = new TreeNode<K,V>
124                                    (h, e.key, e.val, null, null);
125                                if ((h & n) == 0) {
126                                    if ((p.prev = loTail) == null)
127                                        lo = p;
128                                    else
129                                        loTail.next = p;
130                                    loTail = p;
131                                    ++lc;
132                                }
133                                else {
134                                    if ((p.prev = hiTail) == null)
135                                        hi = p;
136                                    else
137                                        hiTail.next = p;
138                                    hiTail = p;
139                                    ++hc;
140                                }
141                            }
142                            //如果扩容后已经不再需要tree的结构 反向转换为链表结构
143                            ln = (lc <= UNTREEIFY_THRESHOLD) ? untreeify(lo) :
144                                (hc != 0) ? new TreeBin<K,V>(lo) : t;
145                            hn = (hc <= UNTREEIFY_THRESHOLD) ? untreeify(hi) :
146                                (lc != 0) ? new TreeBin<K,V>(hi) : t;
147                             //在nextTable的i位置上插入一个链表    
148                            setTabAt(nextTab, i, ln);
149                            //在nextTable的i+n的位置上插入另一个链表
150                            setTabAt(nextTab, i + n, hn);
151                             //在table的i位置上插入forwardNode节点  表示已经处理过该节点
152                            setTabAt(tab, i, fwd);
153                            //设置advance为true 返回到上面的while循环中 就可以执行i--操作
154                            advance = true;
155                        }
156                    }
157                }
158            }
159        }
160    }

 


 插入:

 1 final V putVal(K key, V value, boolean onlyIfAbsent) {
 2         //key和value都不能为空
 3         if (key == null || value == null) throw new NullPointerException();
 4         //计算hash值,让hash值分布更均匀
 5         int hash = spread(key.hashCode());
 6         int binCount = 0;
 7         //什么时候插入成功,什么时候跳出
 8         for (Node<K,V>[] tab = table;;) {
 9             Node<K,V> f; int n, i, fh;
10             //如果table为空的话,进行初始化操作
11             if (tab == null || (n = tab.length) == 0)
12                 tab = initTable();
13             //根据hash值计算出在table里面的位置,返回table[i]这个节点信息,赋值给f 
14             else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
15                 //如果这个位置没有值 ,直接放进去,不需要加锁
16                 if (casTabAt(tab, i, null,
17                              new Node<K,V>(hash, key, value, null)))
18                     break;                   // no lock when adding to empty bin
19             }
20             //如果当前的这个节点正在扩容,那就帮助扩容线程进行扩容
21             else if ((fh = f.hash) == MOVED)
22                 tab = helpTransfer(tab, f);
23             else {
24                 V oldVal = null;
25                 //对节点加锁
26                 synchronized (f) {
27                     //再次判断,多线程下有可能会出问题
28                     if (tabAt(tab, i) == f) {
29                         //fh〉0 说明这个节点是一个链表的节点 不是树的节点
30                         if (fh >= 0) {
31                             binCount = 1;
32                             //在这里遍历链表所有的结点
33                             for (Node<K,V> e = f;; ++binCount) {
34                                 K ek;
35                                 //如果hash值和key值相同  则修改对应结点的value值
36                                 if (e.hash == hash &&
37                                     ((ek = e.key) == key ||
38                                      (ek != null && key.equals(ek)))) {
39                                     oldVal = e.val;
40                                     if (!onlyIfAbsent)
41                                         e.val = value;
42                                     break;
43                                 }
44                                 Node<K,V> pred = e;
45                                 //如果遍历到了最后一个结点,那么就证明新的节点需要插入 就把它插入在链表尾部
46                                 if ((e = e.next) == null) {
47                                     pred.next = new Node<K,V>(hash, key,
48                                                               value, null);
49                                     break;
50                                 }
51                             }
52                         }
53                         //如果这个节点是树节点,就按照树的方式插入值
54                         else if (f instanceof TreeBin) {
55                             Node<K,V> p;
56                             binCount = 2;
57                             if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
58                                                            value)) != null) {
59                                 oldVal = p.val;
60                                 if (!onlyIfAbsent)
61                                     p.val = value;
62                             }
63                         }
64                     }
65                 }
66                 if (binCount != 0) {
67                     //如果链表长度已经达到临界值8 就需要把链表转换为树结构
68                     if (binCount >= TREEIFY_THRESHOLD)
69                         treeifyBin(tab, i);
70                     if (oldVal != null)
71                         return oldVal;
72                     break;
73                 }
74             }
75         }
76          //将当前ConcurrentHashMap的元素数量+1
77         addCount(1L, binCount);
78         return null;
79     }

 

 


 

参考:

https://www.jianshu.com/p/f6730d5784ad

http://www.importnew.com/22007.html

转载于:https://www.cnblogs.com/xmzJava/p/8353032.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/252381.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

BundleFusion那些事儿

背景&#xff1a;前面几篇博客中写了很多关于BundleFusion的东西&#xff0c;主要包括bundlefusion的论文阅读笔记&#xff0c;.sens数据集的生成等&#xff0c;经过最近几天的工作&#xff0c;我对bundlefusion又有了新的技术积累&#xff0c;在这里整理一下&#xff0c;也算是…

Oracle Study之--Oracle 11gR2通过RMAN克隆数据库

Oracle Study之--Oracle 11gR2通过RMAN克隆数据库Purpose of Database Duplication A duplicate database is useful for a variety of purposes, most of which involve testing. You can perform the following tasks in a duplicate database: Test backup and recovery pro…

手机录音ogg格式怎么转换mp3

Ogg这种音频格式刚出来的时候大家是非常热爱的&#xff0c;但是随着时代的发展&#xff0c;这种音频格式已经已经被取代了&#xff0c;现在呢走在音频格式前端的是MP3格式&#xff0c;这是大家都比较熟悉的&#xff0c;但是我们还是会经常下载到ogg这种格式的音频&#xff0c;就…

[机器学习] 推荐系统之协同过滤算法(转)

[机器学习]推荐系统之协同过滤算法 在现今的推荐技术和算法中&#xff0c;最被大家广泛认可和采用的就是基于协同过滤的推荐方法。本文将带你深入了解协同过滤的秘密。下面直接进入正题. 1. 什么是推荐算法 推荐算法最早在1992年就提出来了&#xff0c;但是火起来实际上是最近这…

BundleFusion代码框架讲解

背景&#xff1a;前面用了几篇文章来记录和总结了&#xff0c;我在研究bundlefusion过程中遇到的一些问题以及解决方法&#xff0c;本来想实现给bundlefusion输入先验轨迹&#xff0c;然后让其根据给定的轨迹进行重建&#xff0c;这样即便在环境比较恶劣的情况下&#xff0c;也…

BundlePhobia

1、BundlePhobia用于分析npm package的依赖、bundle后的大小、下载速度预估等等&#xff0c;帮助你在引用一个package之前了解引入该package的代价。 2、也可以将项目的package.json文件上传&#xff0c;BundlePhobia会帮你评估项目中所有包的大小和加载速度。

VFL演示样例

VFL演示样例 上篇文章向大家介绍了VFL的基本的语法点&#xff0c;假设对下面演示样例不熟的童鞋&#xff0c;能够前去參考。废话不多说。我们直接来看演示样例。演示样例一 将五个大小同样、颜色不同的view排成一行&#xff0c;view间的间隔为15px,第一个view的间隔与屏幕的左边…

ubuntu16.04 + ros-kinetic 配置cartographer

其实一直以来都感觉纯视觉SLAM很难落地产品&#xff0c;所以一直在找机会学习激光slam,之前也在深蓝学院上买了一个激光salm的课程&#xff0c;惭愧&#xff0c;至今也没开始学呢&#xff0c;年底之前&#xff0c;我想工作之余研究一下激光slam和ros&#xff0c;我感觉这两个东…

virtualbox中安装ubuntu

为什么80%的码农都做不了架构师&#xff1f;>>> virtualboxubuntu 安装virtualbox&#xff0c;当前版本是6.0.4下载ubuntu安装盘&#xff0c;建议lubuntu&#xff0c;链接是http://mirrors.ustc.edu.cn/ubuntu-cdimage/lubuntu/releases/18.04.2/release/lubuntu-1…

面向对象重写(override)与重载(overload)区别

一、重写&#xff08;override&#xff09; override是重写&#xff08;覆盖&#xff09;了一个方法&#xff0c;以实现不同的功能。一般是用于子类在继承父类时&#xff0c;重写&#xff08;重新实现&#xff09;父类中的方法。 重写&#xff08;覆盖&#xff09;的规则&#…

cartographer学习笔记--如何保存cartagrapher_ros建好的地图

今天开始跟着网友大佬学习cartographer. 1. 如何保存cartographer的地图数据 在运行cartographer过程中可以随时保存建好的地图&#xff0c;步骤如下&#xff1a; 首先是重新打开一个terminal, 如果你没有将你的cartographer_ros下的setup.bash文件写入到.bashrc中&#xff…

Java微信公众号开发(五)—— SVN版本控制工具

1 作用 两个疑问&#xff1a; 什么是版本控制&#xff1f;为什么要用版本控制工具&#xff1f;作用&#xff1a; 受保护受约束合作开发中&#xff0c;版本控制工具更重要的作用就是让开发者更好地协作&#xff0c;每个人的代码既能互相调用&#xff0c;来共同完成一个较大的功…

Linux之《荒岛余生》(二)CPU篇

为什么80%的码农都做不了架构师&#xff1f;>>> 温馨提示&#xff0c;动图已压缩&#xff0c;流量党放心查看。CPU方面内容不多&#xff0c;我们顺便学点命令。本篇是《荒岛余生》系列第二篇&#xff0c;垂直观测CPU。其余参见&#xff1a; Linux之《荒岛余生》&am…

Ubuntu16.04上安装kitti2bag

kitti2bag是一个可以将kitti数据集转换为bag文件的工具&#xff0c;可以直接通过pip进行安装。由于kitti2bag中使用到ros&#xff0c;所以安装时你使用的python版本应该是2.7的因为ros只有在Python2.7时才能正常工作。比如说我&#xff0c;我安装了conda&#xff0c;在conda中安…

UICollectionView 具体解说学习

UICollectionView 和UITableView非常像,是APPLE公司在iOS 6后推出的用于处理图片这类UITableView 布局困难的控件,和UITableView 一样,它也有自己的Datasource和delegate。以下具体说下像这种方式的效果. 首先来看看UICollectionView 的DataSource。protocol UICollectionView…

ServiceNow 中关于UI Action 在portal端的使用

在 portal端是可以使用Form和UI Action的&#xff0c;例如&#xff1a;var data.f $sp.getForm()&#xff1b;//需要添加上相应参数在开箱组件Form的Server script中就有如下代码&#xff1a;data.f $sp.getForm(data.table, data.sys_id, data.query, data.view);data.f对象中…

系统安全题目(二)

1、在 php mysql apache 架构的web服务中输入GET参数 index.php?a1&a2&a3 服务器端脚本 index.php 中$GET[a] 的值是&#xff1f;正确答案: C A 1B 2C 3D 1,2,3 2、以下哪些不是CSRF漏洞的防御方案&#xff1f;正确答案: D A 检测HTTPrefererB 使用随机tokenC 使用验…

ceres-solver学习笔记

前一段时间总有一个想法&#xff0c;那就是&#xff0c;我只直到视觉slam是远远不够的&#xff0c;激光slam仍然是一个比较稳妥的技术&#xff0c;好落地&#xff0c;应用广泛&#xff0c;我想着&#xff0c;如果我学会了会大大增加自己的核心竞争力&#xff0c;所以我抽时间开…

几款常见的视频格式转换器

在短视频占半壁江山的时候&#xff0c;关于体积、格式等成了困扰人们的因素&#xff0c;视频太大不利于传播&#xff0c;比如微信里就限制了传输的大小不得超过20M&#xff0c;所以其实说起来工作上QQ的性能远超微信。今天这里小编给大家总结几款常用的视频转换器&#xff0c;希…

egg(110,111,112)--egg之微信支付

微信支付前的准备工作 准备工作 准备工作&#xff1a;个体工商户、企业、政府及事业单位。需要获取内容 appid&#xff1a;应用 APPID&#xff08;必须配置&#xff0c;开户邮件中可查看&#xff09;MCHID&#xff1a;微信支付商户号&#xff08;必须配置&#xff0c;开户邮件中…