一、简单回顾ConcurrentHashMap在jdk1.7中的设计

先简单看下ConcurrentHashMap类在jdk1.7中的设计,其基本结构如图所示:

每一个segment都是一个HashEntry<K,V>[] table, table中的每一个元素本质上都是一个HashEntry的单向队列。比如table[3]为首节点,table[3]->next为节点1,之后为节点2,依次类推。

  1. public class ConcurrentHashMap<K, V> extends AbstractMap<K, V>
  2. implements ConcurrentMap<K, V>, Serializable {
  3.  
  4. // 将整个hashmap分成几个小的map,每个segment都是一个锁;与hashtable相比,这么设计的目的是对于put, remove等操作,可以减少并发冲突,对
  5. // 不属于同一个片段的节点可以并发操作,大大提高了性能
  6. final Segment<K,V>[] segments;
  7.  
  8. // 本质上Segment类就是一个小的hashmap,里面table数组存储了各个节点的数据,继承了ReentrantLock, 可以作为互拆锁使用
  9. static final class Segment<K,V> extends ReentrantLock implements Serializable {
  10. transient volatile HashEntry<K,V>[] table;
  11. transient int count;
  12. }
  13.  
  14. // 基本节点,存储Key, Value值
  15. static final class HashEntry<K,V> {
  16. final int hash;
  17. final K key;
  18. volatile V value;
  19. volatile HashEntry<K,V> next;
  20. }
  21. }

二、在jdk1.8中主要做了2方面的改进

改进一:取消segments字段,直接采用transient volatile HashEntry<K,V>[] table保存数据,采用table数组元素作为锁,从而实现了对每一行数据进行加锁,进一步减少并发冲突的概率。

改进二:将原先table数组+单向链表的数据结构,变更为table数组+单向链表+红黑树的结构。对于hash表来说,最核心的能力在于将key hash之后能均匀的分布在数组中。如果hash之后散列的很均匀,那么table数组中的每个队列长度主要为0或者1。但实际情况并非总是如此理想,虽然ConcurrentHashMap类默认的加载因子为0.75,但是在数据量过大或者运气不佳的情况下,还是会存在一些队列长度过长的情况,如果还是采用单向列表方式,那么查询某个节点的时间复杂度为O(n);因此,对于个数超过8(默认值)的列表,jdk1.8中采用了红黑树的结构,那么查询的时间复杂度可以降低到O(logN),可以改进性能。

为了说明以上2个改动,看一下put操作是如何实现的。

  1. final V putVal(K key, V value, boolean onlyIfAbsent) {
  2. if (key == null || value == null) throw new NullPointerException();
  3. int hash = spread(key.hashCode());
  4. int binCount = 0;
  5. for (Node<K,V>[] tab = table;;) {
  6. Node<K,V> f; int n, i, fh;
  7. // 如果table为空,初始化;否则,根据hash值计算得到数组索引i,如果tab[i]为空,直接新建节点Node即可。注:tab[i]实质为链表或者红黑树的首节点。
  8. if (tab == null || (n = tab.length) == 0)
  9. tab = initTable();
  10. else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
  11. if (casTabAt(tab, i, null,
  12. new Node<K,V>(hash, key, value, null)))
  13. break; // no lock when adding to empty bin
  14. }
  15. // 如果tab[i]不为空并且hash值为MOVED,说明该链表正在进行transfer操作,返回扩容完成后的table。
  16. else if ((fh = f.hash) == MOVED)
  17. tab = helpTransfer(tab, f);
  18. else {
  19. V oldVal = null;
  20. // 针对首个节点进行加锁操作,而不是segment,进一步减少线程冲突
  21. synchronized (f) {
  22. if (tabAt(tab, i) == f) {
  23. if (fh >= 0) {
  24. binCount = 1;
  25. for (Node<K,V> e = f;; ++binCount) {
  26. K ek;
  27. // 如果在链表中找到值为key的节点e,直接设置e.val = value即可。
  28. if (e.hash == hash &&
  29. ((ek = e.key) == key ||
  30. (ek != null && key.equals(ek)))) {
  31. oldVal = e.val;
  32. if (!onlyIfAbsent)
  33. e.val = value;
  34. break;
  35. }
  36. // 如果没有找到值为key的节点,直接新建Node并加入链表即可。
  37. Node<K,V> pred = e;
  38. if ((e = e.next) == null) {
  39. pred.next = new Node<K,V>(hash, key,
  40. value, null);
  41. break;
  42. }
  43. }
  44. }
  45. // 如果首节点为TreeBin类型,说明为红黑树结构,执行putTreeVal操作。
  46. else if (f instanceof TreeBin) {
  47. Node<K,V> p;
  48. binCount = 2;
  49. if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
  50. value)) != null) {
  51. oldVal = p.val;
  52. if (!onlyIfAbsent)
  53. p.val = value;
  54. }
  55. }
  56. }
  57. }
  58. if (binCount != 0) {
  59. // 如果节点数>=8,那么转换链表结构为红黑树结构。
  60. if (binCount >= TREEIFY_THRESHOLD)
  61. treeifyBin(tab, i);
  62. if (oldVal != null)
  63. return oldVal;
  64. break;
  65. }
  66. }
  67. }
  68. // 计数增加1,有可能触发transfer操作(扩容)。
  69. addCount(1L, binCount);
  70. return null;
  71. }

另外,在其他方面也有一些小的改进,比如新增字段 transient volatile CounterCell[] counterCells; 可方便的计算hashmap中所有元素的个数,性能大大优于jdk1.7中的size()方法。

三、ConcurrentHashMap jdk1.7、jdk1.8性能比较

测试程序如下:

  1. public class CompareConcurrentHashMap {
  2. private static ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<String, Integer>(40000);
  3.  
  4. public static void putPerformance(int index, int num) {
  5. for (int i = index; i < (num + index) ; i++)
  6. map.put(String.valueOf(i), i);
  7. }
  8. public static void getPerformance2() {
  9. long start = System.currentTimeMillis();
  10. for (int i = 0; i < 400000; i++)
  11. map.get(String.valueOf(i));
  12. long end = System.currentTimeMillis();
  13. System.out.println("get: it costs " + (end - start) + " ms");
  14. }
  15.  
  16. public static void main(String[] args) throws InterruptedException {
  17. long start = System.currentTimeMillis();
  18. final CountDownLatch cdLatch = new CountDownLatch(4);
  19. for (int i = 0; i < 4; i++) {
  20. final int finalI = i;
  21. new Thread(new Runnable() {
  22. public void run() {
  23. CompareConcurrentHashMap.putPerformance(100000 * finalI, 100000);
  24. cdLatch.countDown();
  25. }
  26. }).start();
  27. }
  28. cdLatch.await();
  29. long end = System.currentTimeMillis();
  30. System.out.println("put: it costs " + (end - start) + " ms");
  31. CompareConcurrentHashMap.getPerformance2();
  32. }
  33. }

程序运行多次后取平均值,结果如下:

四、Collections.synchronizedList和CopyOnWriteArrayList性能分析

CopyOnWriteArrayList在线程对其进行变更操作的时候,会拷贝一个新的数组以存放新的字段,因此写操作性能很差;而Collections.synchronizedList读操作采用了synchronized,因此读性能较差。以下为测试程序:

  1. public class App {
  2. private static List<String> arrayList = Collections.synchronizedList(new ArrayList<String>());
  3. private static List<String> copyOnWriteArrayList = new CopyOnWriteArrayList<String>();
  4. private static CountDownLatch cdl1 = new CountDownLatch(2);
  5. private static CountDownLatch cdl2 = new CountDownLatch(2);
  6. private static CountDownLatch cdl3 = new CountDownLatch(2);
  7. private static CountDownLatch cdl4 = new CountDownLatch(2);
  8.  
  9. static class Thread1 extends Thread {
  10. @Override
  11. public void run() {
  12. for (int i = 0; i < 10000; i++)
  13. arrayList.add(String.valueOf(i));
  14. cdl1.countDown();
  15. }
  16. }
  17.  
  18. static class Thread2 extends Thread {
  19. @Override
  20. public void run() {
  21. for (int i = 0; i < 10000; i++)
  22. copyOnWriteArrayList.add(String.valueOf(i));
  23. cdl2.countDown();
  24. }
  25. }
  26.  
  27. static class Thread3 extends Thread1 {
  28. @Override
  29. public void run() {
  30. int size = arrayList.size();
  31. for (int i = 0; i < size; i++)
  32. arrayList.get(i);
  33. cdl3.countDown();
  34. }
  35. }
  36.  
  37. static class Thread4 extends Thread1 {
  38. @Override
  39. public void run() {
  40. int size = copyOnWriteArrayList.size();
  41. for (int i = 0; i < size; i++)
  42. copyOnWriteArrayList.get(i);
  43. cdl4.countDown();
  44. }
  45. }
  46.  
  47. public static void main(String[] args) throws InterruptedException {
  48. long start1 = System.currentTimeMillis();
  49. new Thread1().start();
  50. new Thread1().start();
  51. cdl1.await();
  52. System.out.println("arrayList add: " + (System.currentTimeMillis() - start1));
  53.  
  54. long start2 = System.currentTimeMillis();
  55. new Thread2().start();
  56. new Thread2().start();
  57. cdl2.await();
  58. System.out.println("copyOnWriteArrayList add: " + (System.currentTimeMillis() - start2));
  59.  
  60. long start3 = System.currentTimeMillis();
  61. new Thread3().start();
  62. new Thread3().start();
  63. cdl3.await();
  64. System.out.println("arrayList get: " + (System.currentTimeMillis() - start3));
  65.  
  66. long start4 = System.currentTimeMillis();
  67. new Thread4().start();
  68. new Thread4().start();
  69. cdl4.await();
  70. System.out.println("copyOnWriteArrayList get: " + (System.currentTimeMillis() - start4));
  71. }
  72. }

结果如下:

http://www.cnblogs.com/everSeeker/p/5601861.html

Java并发编程总结4——ConcurrentHashMap在jdk1.8中的改进(转)的更多相关文章

  1. Java并发编程总结4——ConcurrentHashMap在jdk1.8中的改进

    一.简单回顾ConcurrentHashMap在jdk1.7中的设计 先简单看下ConcurrentHashMap类在jdk1.7中的设计,其基本结构如图所示: 每一个segment都是一个HashE ...

  2. Java并发编程系列-(9) JDK 8/9/10中的并发

    9.1 CompletableFuture CompletableFuture是JDK 8中引入的工具类,实现了Future接口,对以往的FutureTask的功能进行了增强. 手动设置完成状态 Co ...

  3. Java并发编程笔记之ConcurrentHashMap原理探究

    在多线程环境下,使用HashMap进行put操作时存在丢失数据的情况,为了避免这种bug的隐患,强烈建议使用ConcurrentHashMap代替HashMap. HashTable是一个线程安全的类 ...

  4. java 并发容器一之ConcurrentHashMap(基于JDK1.8)

    上一篇文章简单的写了一下,BoundedConcurrentHashMap,觉得https://www.cnblogs.com/qiaoyutao/p/10903813.html用的并不多:今天着重写 ...

  5. 《Java并发编程实战》读书笔记(更新中)

    一.简介 1.多线程编程要注意的几点: 安全性:永远不发生糟糕的事情 活跃性:某件正确的事情最终会发生(不会发生无限循环或者死锁) 性能:正确的事尽快发生(上下文切换消耗之类的) 二.线程安全 1.为 ...

  6. Java 并发编程(三)为线程安全类中加入新的原子操作

    Java 类库中包括很多实用的"基础模块"类.通常,我们应该优先选择重用这些现有的类而不是创建新的类.:重用能减少开发工作量.开发风险(由于现有类都已经通过測试)以及维护成本.有时 ...

  7. java并发编程——并发容器

    概述 java cocurrent包提供了很多并发容器,在提供并发控制的前提下,通过优化,提升性能.本文主要讨论常见的并发容器的实现机制和绝妙之处,但并不会对所有实现细节面面俱到. 为什么JUC需要提 ...

  8. JAVA并发编程J.U.C学习总结

    前言 学习了一段时间J.U.C,打算做个小结,个人感觉总结还是非常重要,要不然总感觉知识点零零散散的. 有错误也欢迎指正,大家共同进步: 另外,转载请注明链接,写篇文章不容易啊,http://www. ...

  9. Java并发编程系列-(5) Java并发容器

    5 并发容器 5.1 Hashtable.HashMap.TreeMap.HashSet.LinkedHashMap 在介绍并发容器之前,先分析下普通的容器,以及相应的实现,方便后续的对比. Hash ...

随机推荐

  1. JavaScript之firstChild属性、lastChild属性、nodeValue属性学习

    1.数组元素childNodes[0]有更直观易读的优点,这边在介绍一个有同样功能的属性,且更加语义化-------->firstChild属性 假设我们需要目标元素节点下的所有子元素中的第一个 ...

  2. Ext.Net 使用总结之GridPanel中的选中行

    1.判断GridPanel中是否选中了某行 if (!GridPanel1.hasSelection()) { Ext.Msg.alert("提示", "请选择记录!&q ...

  3. jQuery插件开发方法

    jQuery如此流行,各式各样的jQuery插件也是满天飞.你有没有想过把自己的一些常用的JS功能也写成jQuery插件呢?如果你的答案是肯定的,那么来吧!和我一起学写jQuery插件吧! 很多公司的 ...

  4. AJAX背景技术介绍

    AJAX全称为“Asynchronous JavaScript and XML”(异步JavaScript和XML),是指一种创建交互式网页应用的网页开发技术. 主要包含了以下几种技术: Ajax(A ...

  5. oracle数据库获取指定表的列的相关信息

    1.很多时候我们需要从数据库中获取指定表的所有列的相关属性,如 name,commens,datatype,datalength,pk等.下面就是制定的语句. select c.TABLE_NAME ...

  6. matlab GUI之自定义菜单小结

    自定义菜单 1.uimenu对象 h=uimenu('PropertyName','ProperValue') h=uimenu(parent,'PropertyName','ProperValue' ...

  7. python xlrd对excel的读取功能

    工作簿 xlrd.open_workbook('test.xls') workbook.dump() workbook.nsheets workbook.sheets() workbook.sheet ...

  8. 在python文本编辑器里如何设置Tab为4个空格

    python中缩进一般为四个空格,我总结3种常用编辑器中种如何设置Tab键为四个空格 第一种:下载python3.5时自带de 一个IDLE编辑器 在Options选项下的Configure IDLE ...

  9. 四轴飞行器1.2.3 STM32F407时钟配置和升级标准库文件

    原创文章,欢迎转载,转载请注明出处 这个星期进度比较慢哈,只有周末和晚上下班回来才能做,事件不连续,琐碎的事情又比较多,挺烦的,有多琐碎呢?           1.本人有点小强迫症哈,虽然RTT将文 ...

  10. WireShark抓包时TCP数据包出现may be caused by ip checksum offload

    最近用WireShark抓包时发现TCP数据包有报错:IP Checksum Offload,经过查阅资料终于找到了原因 总结下来就是wireshark抓到的数据包提示Checksum错误,是因为它截 ...