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,一经查实,立即删除!

相关文章

XML Schema 基本结构

<?xml version1.0?> <Schema name"cangchuSchema" metamodelVersion"4.0"><PhysicalSchema><Table name"highway_toll"><Key><Column name"uid"/></Key></Table><Table name&qu…

关于系统自带 .NET Framework 版本的说明

系统自带版本&#xff1a; Windows XP (SP3) .NET Framework 1.1Windows 7 (SP1) .NET Framework 3.5.1Windows 8.1 .NET Framework 4.5.1Windows 10 (1507) .NET Framework 4.6Windows 10 (1511) .NET Framework 4.6.1Windows 10 (1607) .NET Framework 4.6.2Windows 10 (1703…

BundleFusion那些事儿

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

问题:图片怎么保存到数据库, 以及怎么把图片从数据库中取出来使用?(已解决)...

简单&#xff0c;不保存图片到数据库&#xff0c;而是图片的路径。 也就是说&#xff0c;先把图片下载到服务器的指定目录下&#xff0c;然后&#xff0c;在把相对的路径保存到数据库中。 如果下次获取图片&#xff0c;就访问数据库&#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…

ubuntu16.04安装evo

背景:这已经是我第二次尝试安装evo了,最近因为在bundlefusion加入groundtruth的问题搞的很烦躁,我怀疑是不是我给定的groundtruth是不是不正确,所以我就写python脚本,获取计算生成的位姿数据,写入的groundtruth位姿数据.获取数据后,将数据可视化就成了一个很重要的问题,我还是打…

前端性能优化之gzip

gzip是GNUzip的缩写&#xff0c;它是一个GNU自由软件的文件压缩程序。它最早由Jean-loup Gailly和Mark Adler创建&#xff0c;用于UNⅨ系统的文件压缩。我们在Linux中经常会用到后缀为.gz的文件&#xff0c;它们就是GZIP格式的。现今已经成为Internet 上使用非常普遍的一种数据…

luogu2770 航空路线问题 网络流

题目大意&#xff1a; 给定一张航空图&#xff0c;图中顶点代表城市&#xff0c;边代表 2 城市间的直通航线。现要求找出一条满足下述限制条件的且途经城市最多的旅行路线。(1)从最西端城市出发&#xff0c;单向从西向东途经若干城市到达最东端城市&#xff0c;然后再单向从东向…

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

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

TP3.2设置URL伪静态满足更好的SEO效果

URL伪静态通常是为了满足更好的SEO效果&#xff0c;ThinkPHP支持伪静态URL设置&#xff0c;可以通过设置URL_HTML_SUFFIX参数随意在URL的最后增加你想要的静态后缀&#xff0c;而不会影响当前操作的正常执行。 例如&#xff0c;我们设置 URL_HTML_SUFFIX>shtml 的话&#xf…

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

[机器学习]推荐系统之协同过滤算法 在现今的推荐技术和算法中&#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的间隔与屏幕的左边…

evo实用指令指南

下面这篇文章中有比较具体的关于evo的安装以及使用的介绍&#xff0c;其中一点很重要&#xff0c;就是可以把euroc形式的.csv的轨迹格式转换为tum格式的轨迹。 https://zhuanlan.zhihu.com/p/88223106#single evo_traj tum orb_slam2_tum.txt --reftum_groundtruth.txt -p --pl…

MailUtils

/***包名:com.thinkgem.jeesite.test*描述:package com.thinkgem.jeesite.test;*/ package com.thinkgem.jeesite.test;import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.Properties; import java.util.regex.Matcher; import java.u…

ES6遍历对象

遍历对象 E S 6 一共有 5 种方法可以遍历对象的属性 。 for ... in for . . . in 循环遍历对象自身的和继承的可枚举属性&#xff08;不含 Symbol 属性&#xff09;。 Object.keys(obj)Object . keys 返回 一个数组&#xff0c;包括对象自身的&#xff08;不含继承的 &#xff…

SpringMvc中ModelAndView模型的应用

/** * 目标方法的返回值可以是 ModelAndView 类型。 * 其中可以包含视图和模型信息 * SpringMVC 会把 ModelAndView 的 model 中数据放入到 request 域对象中. * return */ RequestMapping("/testModelAndView") public ModelAndView testModelAndView(){ String v…

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…