java基础解析系列(五)---HashMap并发下的问题以及HashTable和CurrentHashMap的区别
java基础解析系列(五)---HashMap并发下的问题以及HashTable和CurrentHashMap的区别
目录
- java基础解析系列(一)---String、StringBuffer、StringBuilder
- java基础解析系列(二)---Integer
- java基础解析系列(三)---HashMap
- java基础解析系列(四)---LinkedHashMap的原理及LRU算法的实现
- 这是我的博客目录,欢迎阅读
HashMap造成的死循环
resize分析
void resize(int newCapacity) {
472 Entry[] oldTable = table;
473 int oldCapacity = oldTable.length;
474 if (oldCapacity == MAXIMUM_CAPACITY) {
475 threshold = Integer.MAX_VALUE;
476 return;
477 }
478
479 Entry[] newTable = new Entry[newCapacity];
480 transfer(newTable);
481 table = newTable;
482 threshold = (int)(newCapacity * loadFactor);
483 }
void transfer(Entry[] newTable) {
489 Entry[] src = table;
490 int newCapacity = newTable.length;
491 for (int j = 0; j < src.length; j++) {
492 Entry<K,V> e = src[j];
493 if (e != null) {
494 src[j] = null;
495 do {
496 Entry<K,V> next = e.next;//用于判断后面循环是否继续
497 int i = indexFor(e.hash, newCapacity);
498 e.next = newTable[i];
499 newTable[i] = e;
500 e = next;
501 } while (e != null);
502 }
503 }
504 }
- 当hashmap大小超过阈值的时候,会进行扩容
- 看看第497到500行代码做了什么
- 取出原table中的一个Entry e
- 计算该Entry e的在新表的新下标,然后将新table该下标的Entry从新table拿出来,作为Entry e的next,再把将e放到newTable,newTable[i]=e
- 那么其实他做的就是在原table的Entry取出来,计算他的新下标,然后将这个Entry放入新的table,放入新table的时候,是做为链头,原来的Entry接在后面,实际上就相当于链表的头插法
并发情况下的resize
- put完成之后的结果

- 如果此时有两个线程,线程一完成resize,结果如下

此前线程二之前只执行了第一层Entry<K,V> next = e.next,所以对于线程二来说,此时e为3,next是7(这个是判断后面循环是否终止),然后继续resize
执行497到501的代码

- while(e!=null),此时e为7,e不为空,进入第二次循环
- next=e.next,即next为7的next(这个是判断后面循环是否终止),也就是3(线程一的结果),把7放到链表前头

while(e!=null),此时e=3,e不等于null,进入第三次循环
next=e.next(这个是判断后面循环是否终止),即3的next,也就是null(造成后面循环终止)
放置3这个Entry,3的next设为7(e.next = newTable[i];),而上一步7的next是3,这样就造成了一个循环

- while(e!=null),e为null循环终止
- 那么如果此时get一个键,如果这个键的hash值刚好和3相同,那么这个时候就会遍历链表进行查找,而这个链表是个循环链表,就会造成死循环
- 因此hashmap并不是线程安全
HashTable
对比
public synchronized V get(Object key){}
public synchronized V put(K key, V value) {}
public synchronized V remove(Object key){}
- 用一个表来描述HashMap和HashTable的主要区别
| 对比 | HashMap | HashTable |
|---|---|---|
| 键值 | 键和value允许null | 不行 |
| synchronized | 非synchronzied | synchronized |
| 单线程情况下速度 | 快 | 慢 |
| 扩容方式 | 2倍 | 2倍+1 |
| 容量 | 初始为16,必须为2的n次方 | 初始为11 |
缺点
- 单线程情况下,也会加锁
ConcurrentHashMap
HashEntry类
static final class HashEntry<K,V> {
219 final K key;
220 final int hash;
221 volatile V value;
222 final HashEntry<K,V> next;
223
224 HashEntry(K key, int hash, HashEntry<K,V> next, V value) {
225 this.key = key;
226 this.hash = hash;
227 this.next = next;
228 this.value = value;
229 }
230
231 @SuppressWarnings("unchecked")
232 static final <K,V> HashEntry<K,V>[] More ...newArray(int i) {
233 return new HashEntry[i];
234 }
235 }
Segment类
static final class Segment<K,V> extends ReentrantLock implements Serializable {
transient volatile HashEntry<K,V>[] table
315 final float loadFactor;
316
317 Segment(int initialCapacity, float lf) {
318 loadFactor = lf;
319 setTable(HashEntry.<K,V>newArray(initialCapacity));
320 }
321
231 @SuppressWarnings("unchecked")
232 static final <K,V> HashEntry<K,V>[] newArray(int i) {
233 return new HashEntry[i];
234 }
Sets table to new HashEntry array. Call only while holding lock or in constructor.
330
331 void setTable(HashEntry<K,V>[] newTable) {
332 threshold = (int)(newTable.length * loadFactor);
333 table = newTable;
334 }
- Segment继承了ReentrantLock显示锁
- 一个Segement对象维护这一个HashEntry数组
- 构造方法里面调用了newArray方法,这个方法用于创建一个HashEntry数组
CurrentHashMap构造方法
612 public .ConcurrentHashMap(int initialCapacity,
613 float loadFactor, int concurrencyLevel) {
614 if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0)
615 throw new IllegalArgumentException();
616
617 if (concurrencyLevel > MAX_SEGMENTS)
618 concurrencyLevel = MAX_SEGMENTS;
619
620 // Find power-of-two sizes best matching arguments
621 int sshift = 0;
622 int ssize = 1;
623 while (ssize < concurrencyLevel) {
624 ++sshift;
625 ssize <<= 1;
626 }
627 segmentShift = 32 - sshift;
628 segmentMask = ssize - 1;
629 this.segments = Segment.newArray(ssize);
630
631 if (initialCapacity > MAXIMUM_CAPACITY)
632 initialCapacity = MAXIMUM_CAPACITY;
633 int c = initialCapacity / ssize;
634 if (c * ssize < initialCapacity)
635 ++c;
636 int cap = 1;
637 while (cap < c)
638 cap <<= 1;
639
640 for (int i = 0; i < this.segments.length; ++i)
641 this.segments[i] = new Segment<K,V>(cap, loadFactor);
642 }
- 629行创建了一个Segment数组
- 640-641为Segment数组中的每一个Segment创建一个HashEntry数组
- 那么实际上初始化的时候是先创建一个Segemnt数组,然后每个Segment又创建一个HashEntry数组,可以类比二维数组

CurrentHashMap的put方法
public V put(K key, V value) {
908 if (value == null)
909 throw new NullPointerException();
910 int hash = hash(key.hashCode());
911 return segmentFor(hash).put(key, hash, value, false);
912 }
200 final Segment<K,V> segmentFor(int hash) {
201 return segments[(hash >>> segmentShift) & segmentMask];
202 }
- put的时候通过segmentFor找到segments数组的下标,然后在该segemnt存放键值对,实际上就是找到一个HashEntry数组,然后添加到该数组其中一个链表中
Segment的put方法
444 V put(K key, int hash, V value, boolean onlyIfAbsent) {
445 lock();
446 try {
447 int c = count;
448 if (c++ > threshold) // ensure capacity
449 rehash();
450 HashEntry<K,V>[] tab = table;
451 int index = hash & (tab.length - 1);
452 HashEntry<K,V> first = tab[index];
453 HashEntry<K,V> e = first;
454 while (e != null && (e.hash != hash || !key.equals(e.key)))
455 e = e.next;
456
457 V oldValue;
458 if (e != null) {
459 oldValue = e.value;
460 if (!onlyIfAbsent)
461 e.value = value;
462 }
463 else {
464 oldValue = null;
465 ++modCount;
466 tab[index] = new HashEntry<K,V>(key, hash, first, value);
467 count = c; // write-volatile
468 }
469 return oldValue;
470 } finally {
471 unlock();
472 }
473 }
- 在前面已经知道Segment继承了显式锁,从445看出,代码会执行lock方法,也就是加锁,这是对于一个Segment的,那么也就是如果put的时候找到的Segemnt是不一样的,那么put的时候不是锁对象不同就不会产生竞争,这就是相对于HashTable来说的一个优点,不会任何时候都加锁
CurrentHashMap的get方法
795 public V get(Object key) {
796 int hash = hash(key.hashCode());
797 return segmentFor(hash).get(key, hash);
798 }
- 和put一样,先在segments数组中找到一个segment,然后执行他的get方法
Segment的get方法
362 V get(Object key, int hash) {
363 if (count != 0) { // read-volatile
364 HashEntry<K,V> e = getFirst(hash);
365 while (e != null) {
366 if (e.hash == hash && key.equals(e.key)) {
367 V v = e.value;
368 if (v != null)
369 return v;
370 return readValueUnderLock(e); // recheck
371 }
372 e = e.next;
373 }
374 }
375 return null;
376 }
377
- getFirst方法找到在Entry数组中对应位置的链表的链头,然后对链表进行遍历
- 看下370行的readValueUnderLock方法,源码也注释了recheck,作用就是在找当找到对应的键后并且value为null的时候,再进行一次查找。
readValueUnderLock
351 V readValueUnderLock(HashEntry<K,V> e) {
352 lock();
353 try {
354 return e.value;
355 } finally {
356 unlock();
357 }
358 }
- 这次查找会进行加锁,这个过程可能读到最近覆盖的一个非空的value,这是对比HashTable的第二个好处,hashtable是对get用synchronized修饰,CurrentHashMap不会在get的时候全程加锁,减小锁的粒度,甚至不加锁
我觉得分享是一种精神,分享是我的乐趣所在,不是说我觉得我讲得一定是对的,我讲得可能很多是不对的,但是我希望我讲的东西是我人生的体验和思考,是给很多人反思,也许给你一秒钟、半秒钟,哪怕说一句话有点道理,引发自己内心的感触,这就是我最大的价值。(这是我喜欢的一句话,也是我写博客的初衷)
作者:jiajun 出处: http://www.cnblogs.com/-new/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。如果觉得还有帮助的话,可以点一下右下角的【推荐】,希望能够持续的为大家带来好的技术文章!想跟我一起进步么?那就【关注】我吧。
java基础解析系列(五)---HashMap并发下的问题以及HashTable和CurrentHashMap的区别的更多相关文章
- java基础解析系列(三)---HashMap
java基础解析系列(三)---HashMap java基础解析系列 java基础解析系列(一)---String.StringBuffer.StringBuilder java基础解析系列(二)-- ...
- java基础解析系列(六)---深入注解原理及使用
java基础解析系列(六)---注解原理及使用 java基础解析系列(一)---String.StringBuffer.StringBuilder java基础解析系列(二)---Integer ja ...
- java基础解析系列(七)---ThreadLocal原理分析
java基础解析系列(七)---ThreadLocal原理分析 目录 java基础解析系列(一)---String.StringBuffer.StringBuilder java基础解析系列(二)-- ...
- java基础解析系列(八)---fail-fast机制及CopyOnWriteArrayList的原理
fail-fast机制及CopyOnWriteArrayList的原理 目录 java基础解析系列(一)---String.StringBuffer.StringBuilder java基础解析系列( ...
- java基础解析系列(九)---String不可变性分析
java基础解析系列(九)---String不可变性分析 目录 java基础解析系列(一)---String.StringBuffer.StringBuilder java基础解析系列(二)---In ...
- java基础解析系列(十)---ArrayList和LinkedList源码及使用分析
java基础解析系列(十)---ArrayList和LinkedList源码及使用分析 目录 java基础解析系列(一)---String.StringBuffer.StringBuilder jav ...
- java基础解析系列(十一)---equals、==和hashcode方法
java基础解析系列(十一)---equals.==和hashcode方法 目录 java基础解析系列(一)---String.StringBuffer.StringBuilder java基础解析系 ...
- java基础解析系列(六)---注解原理及使用
java基础解析系列(六)---注解原理及使用 java基础解析系列(一)---String.StringBuffer.StringBuilder java基础解析系列(二)---Integer缓存及 ...
- java基础解析系列(四)---LinkedHashMap的原理及LRU算法的实现
java基础解析系列(四)---LinkedHashMap的原理及LRU算法的实现 java基础解析系列(一)---String.StringBuffer.StringBuilder java基础解析 ...
随机推荐
- Java实现简单文件过滤器
输入路径查找该路径下的指定文件类型的文件 代码思路: 想要循环遍历文件夹下所有子文件夹,就要用到递归. 首先判断路径是否存在: 是:获取文件 判断是否文件夹: 是:调用自身,继续获取子文件夹下内容 否 ...
- JVM学习笔记二:垃圾收集算法
垃圾回收要解决的问题: 哪些内存需要回收? 线程私有区域不需要回收,如PC.Stack.Native Stack:Java 堆和方法区需要 什么时候回收? 以后的文章解答 如何回收? 首先进行对象存活 ...
- 华为软件开发云对比Jenkins-JavaWeb项目持续部署方式
一.前言:Jenkins介绍 Jenkins是一个开源软件项目,是基于Java开发的一种持续集成工具,用于监控持续重复的工作,旨在提供一个开放易用的软件平台,使软件的持续集成和持续部署变成可能. 本文 ...
- "=="和equals方法究竟有什么区别?
(单独把一个东西说清楚,然后再说清楚另一个,这样,它们的区别自然就出来了,混在一起说,则很难说清楚) ==操作符专门用来比较两个变量的值是否相等,也就是用于比较变量所对应的内存中所存储的数值是否相同, ...
- MySQL编程基础
本文是关于MySQL编程中的一些基础知识,包括变量和运算符.常用语句.函数. 一.变量与运算符 1.用户会话变量声明:SET @变量名 = 表达式;//即:用户会话变量无需提前定义,直接用赋值语句赋值 ...
- php面试题汇总二(基础篇附答案)
介绍一些php常见面试题及答案,都是平时面试的时候经常会遇到的,小伙伴们仔细了解下吧.接着上一篇php面试题汇总一(基础篇附答案) 1.在PHP中,当前脚本的名称(不包括路径和查询字符串)记录在预定义 ...
- MySQL (五)
1 连接查询简介 将多张表(可以大于2)进行记录的连接(按照某个指定的条件进行数据拼接). 最终结果:记录数可能会有变化,字段书一定会增加(至少两张表的合并). 连接查询:join,使用方式:左表 j ...
- noip普及组2007 奖学金
奖学金 描述 某小学最近得到了一笔赞助,打算拿出其中一部分为学习成绩优秀的前5名学生发奖学金.期末,每个学生都有3门课的成绩:语文.数学.英语.先按总分从高到低排序,如果两个同学总分相同,再按语文成绩 ...
- 如何修改ionic Popup的样式
第一次用ionic的Popup时感觉很好用,但是太丑了.但是又不会修改自定义的样式,只能自己写了个模态框.后来仔细搜索了一下.发现还是可以修改的. var confirmPopup = $ionicP ...
- BigDecimal精确计算及陷阱
BigDecimal通常在涉及到精确计算的时候会用到,下面是自己多次错误使用BigDecimal的总结. 结论: BigDecimal初始化小数时,尽量用字符串形式,例如new BigDecimal( ...