2019独角兽企业重金招聘Python工程师标准>>>

1、首先看下HashMap的put方法。

 final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {Node<K,V>[] tab; Node<K,V> p; int n, i;if ((tab = table) == null || (n = tab.length) == 0)// 1.1 初始化数组长度n = (tab = resize()).length;// 1.2 查询数组对应下标是否有值,下标计算tab[i = (n - 1) & hash] // hash表示通过key.hashcode和key.hashcode的高16位进行异或操作,为什么要这样做?,n-1表示15,二进制表示01111/*** 解释:比如key.hashcode = 10010110101010,那么如何直接拿 key.hashcode & (n-1) 此处n-1 = 15*   10010110101010*            01111*-----------------------*            01010* 此时是否发现key.hashcode的高16位压根就没有进行计算,那么只用低位计算是不是冲突的概率较大?* 这就是为什么将key.hashcode的高位和低位进行^操作后在与15进行&操作*/if ((p = tab[i = (n - 1) & hash]) == null)// 1.3 将对应的值放入数组对应下标tab[i] = newNode(hash, key, value, null);else {// hash 发生碰撞Node<K,V> e; K k;// 并且发现key已存在,那么将新值和旧值互换if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))e = p;else if (p instanceof TreeNode)// 红黑二叉树e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);else {// 链表for (int binCount = 0; ; ++binCount) {// 判断链表的next是否有值if ((e = p.next) == null) {p.next = newNode(hash, key, value, null);// 当链表的数量大于等于8时,链表会自动转换成红黑二叉树,因为链表过长会导致链表的查询效率过低if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1sttreeifyBin(tab, hash);break;}if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))break;p = e;}}if (e != null) { // existing mapping for keyV oldValue = e.value;if (!onlyIfAbsent || oldValue == null)e.value = value;afterNodeAccess(e);return oldValue;}}++modCount;// 当map中的数据大于16*0.75=12 map的初始化大小位16,负载因子0.75if (++size > threshold)resize();afterNodeInsertion(evict);return null;}

2、 通过解读源码发现hashmap不时线程安全
       那么此时hashtable登场 通过源码发现hashtable的put方法上加了synchronized关键字,HashTable容器使用synchronized来保证线程安全,但在线程竞争激烈的情况下HashTable的效率非常低下。因为当一个线程访问HashTable的同步方法时,其他线程访问HashTable的同步方法时,可能会进入阻塞或轮询状态。如线程1使用put进行添加元素,线程2不但不能使用put方法添加元素,并且也不能使用get方法来获取元素,所以竞争越激烈效率越低,此时会发现多线程操作时效率明显过低对吧!那么有办法解决吗?

        public synchronized V put(K key, V value) {// Make sure the value is not nullif (value == null) {throw new NullPointerException();}// Makes sure the key is not already in the hashtable.Entry<?,?> tab[] = table;int hash = key.hashCode();int index = (hash & 0x7FFFFFFF) % tab.length;@SuppressWarnings("unchecked")Entry<K,V> entry = (Entry<K,V>)tab[index];for(; entry != null ; entry = entry.next) {if ((entry.hash == hash) && entry.key.equals(key)) {V old = entry.value;entry.value = value;return old;}}addEntry(hash, key, value, index);return null;}

3、 当前有办法了,ConcurrentHashMap闪亮登场

 /** Implementation for put and putIfAbsent */final V putVal(K key, V value, boolean onlyIfAbsent) {if (key == null || value == null) throw new NullPointerException();int hash = spread(key.hashCode());int binCount = 0;for (Node<K,V>[] tab = table;;) {Node<K,V> f; int n, i, fh;if (tab == null || (n = tab.length) == 0)// 初始化数组,刚才我们提到多线程,难道每个线程都去初始化?下面单独拉出来看下tab = initTable();else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {// 利用cas无锁机制,cas原子操作,在指定位置设定值if (casTabAt(tab, i, null,new Node<K,V>(hash, key, value, null)))break;                   // no lock when adding to empty bin}else if ((fh = f.hash) == MOVED)// 此处map扩容后,数据转移tab = helpTransfer(tab, f);else {// 锁住当前node节点,这样其他线程继续操作时,因为node对象不是同一个了,// 所以不会影响其他线程的操作,这样效率就提高了。V oldVal = null;synchronized (f) {if (tabAt(tab, i) == f) {if (fh >= 0) {binCount = 1;for (Node<K,V> e = f;; ++binCount) {K ek;if (e.hash == hash &&((ek = e.key) == key ||(ek != null && key.equals(ek)))) {oldVal = e.val;if (!onlyIfAbsent)e.val = value;break;}Node<K,V> pred = e;if ((e = e.next) == null) {pred.next = new Node<K,V>(hash, key,value, null);break;}}}else if (f instanceof TreeBin) {Node<K,V> p;binCount = 2;if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,value)) != null) {oldVal = p.val;if (!onlyIfAbsent)p.val = value;}}}}if (binCount != 0) {if (binCount >= TREEIFY_THRESHOLD)treeifyBin(tab, i);if (oldVal != null)return oldVal;break;}}}addCount(1L, binCount);return null;}

4、回到刚才ConcurrentMap初始化 tab = initTable()

  private transient volatile int sizeCtl; // 注意此处sizeCtl为volatileprivate final Node<K,V>[] initTable() {Node<K,V>[] tab; int sc;while ((tab = table) == null || tab.length == 0) {// 第一次进来sizeCtl值默认为0,那么其他线程过来时发现sizeCtl已经变成了-1,// 直接Thread.yield()让出cpu时间片。故此就实现了只有一个线程去初始化数组if ((sc = sizeCtl) < 0)Thread.yield(); // lost initialization race; just spinelse if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {// 将sc的值置为-1try {if ((tab = table) == null || tab.length == 0) {int n = (sc > 0) ? sc : DEFAULT_CAPACITY;@SuppressWarnings("unchecked")Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];table = tab = nt;sc = n - (n >>> 2);}} finally {// 将sizeCtl置为 -1sizeCtl = sc;}break;}}return tab;}

5、ConcurrentMap扩容注意点

ConcurrentMap扩容时,其他线程不能再往map中添加/删除数据了,那么难道其他线程在那边等待,直到扩容完成?当然不是了,其他线程会去帮助扩容线程一起进行扩容,每个线程都会去领取属于自己的任务

    final Node<K,V>[] helpTransfer(Node<K,V>[] tab, Node<K,V> f) {Node<K,V>[] nextTab; int sc;if (tab != null && (f instanceof ForwardingNode) &&(nextTab = ((ForwardingNode<K,V>)f).nextTable) != null) {int rs = resizeStamp(tab.length);while (nextTab == nextTable && table == tab &&(sc = sizeCtl) < 0) {if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||sc == rs + MAX_RESIZERS || transferIndex <= 0)break;if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) {transfer(tab, nextTab);break;}}return nextTab;}return table;}

转载于:https://my.oschina.net/u/3370769/blog/2998760

HashMap 1.8 源码解析以及非线程安全分析相关推荐

  1. Java Executor源码解析(3)—ThreadPoolExecutor线程池execute核心方法源码【一万字】

    基于JDK1.8详细介绍了ThreadPoolExecutor线程池的execute方法源码! 上一篇文章中,我们介绍了:Java Executor源码解析(2)-ThreadPoolExecutor ...

  2. Java Executor源码解析(7)—Executors线程池工厂以及四大内置线程池

    详细介绍了Executors线程池工具类的使用,以及四大内置线程池. 系列文章: Java Executor源码解析(1)-Executor执行框架的概述 Java Executor源码解析(2)-T ...

  3. hashmap与concurrenthashmap源码解析

    hashmap源码解析转载:http://www.cnblogs.com/ITtangtang/p/3948406.html 一.HashMap概述 HashMap基于哈希表的 Map 接口的实现.此 ...

  4. Android技术栈--HashMap和ArrayMap源码解析

    1 总览 WARNING!!:本文字数较多,内容较为完整并且部分内容难度较大,阅读本文需要较长时间,建议读者分段并耐心阅读. 本文会对 Android 中常用的数据结构进行源码解析,包括 HashMa ...

  5. ScheduledThreadPool 源码解析——定时类线程池是如何工作的

    文章目录 引言 一.ScheduledThreadPool 使用示例 1. 延时类的定时任务 `schedule` 2. 延时类,固定周期执行任务 `scheduleAtFixedRate` 3. 延 ...

  6. HashMap(JDK1.8)源码解析

    文章目录 简介 特点 数据结构 JDK1.8之前 JDK1.8之后 JDK1.7 VS JDK1.8 比较 继承关系图 成员变量 构造方法 静态内部类 Node TreeNode 核心方法 hash( ...

  7. 【NDPI】源码解析之深度包检测分析(一)

    (Albert.2019.4.28) 文章目录: 前言: 正文: 一.nDPI深度包检测流程: 二.重要结构体的源码分析 1.ndpi_ethdr.ndpi_iphdr.ndpi_tcphdr.ndp ...

  8. HashMap::put方法源码解析及执行流程图

    HashMap::put方法 类介绍: ​ HashMap 是Java的一个集合类,继承了AbstractMap类,同时实现了Map.Cloneable.Serializable接口 public c ...

  9. 【EventBus】EventBus 源码解析 ( 事件发送 | 线程池中执行订阅方法 )

    文章目录 一.EventBus 中主线程支持类 二.EventBus 中 AsyncPoster 分析 三.AsyncPoster 线程池 Runnable 任务类 一.EventBus 中主线程支持 ...

最新文章

  1. WeeklyBlogging_20100722
  2. 第六十九期: 漫画说算法之什么是一致性哈希?
  3. 大数据批量插入小练习_SqlServer
  4. 怎样能让电脑干干净净的没有各种弹窗广告?
  5. ubuntu编译libid3tag库报错问题解决
  6. 分组交换技术HDLC配置简述
  7. 人机交互论文计算机导论,计算机导论第10章人机交互[精].ppt
  8. 多图片的合并(2种方式,可以设置间距)
  9. log explorer for sql 不存在或访问被拒绝_原创干货 | 未授权访问漏洞批量化
  10. 【Java】编码规范
  11. sketchup 2018下载与安装教程
  12. viso添加多个图注_Visio画图几个技巧
  13. oracle数据库hiredate,数据库hiredate
  14. DINO:自监督ViT的新特性
  15. codeforces1428F Fruit Sequences
  16. 华为云ManageOne北向对接之基本名词概念(一)
  17. Oralce查询当年的数据
  18. Python的.py与Cython的.pxd.pyx.pyd 文件格式之间的主要区别
  19. 【秃头系列】-【本科生毕设论文格式Word】小修改和小问题
  20. WebRTC RTCP RTP Feedback

热门文章

  1. 专访趋势科技CEO陈怡桦:病毒行业需要反省
  2. 三个能够造成重大损失的低技术含量攻击
  3. 用Jenkins自动化构建Android和iOS应用
  4. 2017年大数据的十大发展趋势
  5. ubuntu下解决Ruby安装后缺少openssl的问题
  6. Autodesk 产品二次开发技术研讨会将在上海广州北京相继举行
  7. 安装Phoenix时./sqlline.py执行报错File ./sqlline.py, line 27, in module import argparse ImportError: No ...
  8. Data source rejected establishment of connection, message from server: Too many connections解决办法...
  9. DELL服务器结合nagios硬件监控、报警
  10. Hotpatch潜在的安全风险