java基础-ConcurrentHashMap

jdk7中ConcurrentHashMap的put方法

 1     public V put(K key, V value) {
 2         Segment<K,V> s;
 3         //在并发map中key、value均不能为null
 4         if (value == null)
 5             throw new NullPointerException();
 6         int hash = hash(key);
 7         //j是segments中的index,定位segment,段用于提高并发度,更新时只在段内加锁,不影响其他段,segments的大小是ssize,ssize<=concurrencyLevel,且是2的n次方。
 8         int j = (hash >>> segmentShift) & segmentMask;
 9          //(j << SSHIFT) + SBASE是内存中第j位的segment在segments中的偏移量
10         if ((s = (Segment<K,V>)UNSAFE.getObject          // nonvolatile; recheck
11              (segments, (j << SSHIFT) + SBASE)) == null) //  in ensureSegment
12             //保证第j位segment的存在
13             s = ensureSegment(j);
14         //向第j位的segment存入key-value
15         return s.put(key, hash, value, false);
16     }

segment的put方法,segment可以看做是一个HashMap,但其并没有实现Map,Segment<K,V> extends ReentrantLock implements Serializable,可以看到segment继承了ReentrantLock,其更像是一把锁。

 1 final V put(K key, int hash, V value, boolean onlyIfAbsent) {
 2             //首先尝试获取锁,如果失败则不断扫描hash映射到的桶(以确定是否有相同的key,思想就是即便暂时没有获取到锁,也不要闲着,找些之后要做的事先做着),并尝试获取锁,当尝试获取锁的次数到达阈值,则lock。
 3             HashEntry<K,V> node = tryLock() ? null :
 4                 scanAndLockForPut(key, hash, value);
 5     //之后的逻辑都是在获得锁的前提下进行的
 6             V oldValue;
 7             try {
 8                 //segment其实是一个map结构(但没有实现Map接口),这里的table就类似于HashMap中的table
 9                 HashEntry<K,V>[] tab = table;
10                 //index即桶的索引
11                 int index = (tab.length - 1) & hash;
12                 //取得桶中的首节点
13                 HashEntry<K,V> first = entryAt(tab, index);
14                 //循环逻辑类似HashMap,遇到相同的key就替换,遍历置尾节点仍没有找到相同的key则插入
15                 for (HashEntry<K,V> e = first;;) {
16                     if (e != null) {
17                         K k;
18                         if ((k = e.key) == key ||
19                             (e.hash == hash && key.equals(k))) {
20                             oldValue = e.value;
21                             if (!onlyIfAbsent) {
22                                 e.value = value;
23                                 ++modCount;
24                             }
25                             break;
26                         }
27                         e = e.next;
28                     }
29                     else {
30                         if (node != null)
31                             node.setNext(first);
32                         else
33                             node = new HashEntry<K,V>(hash, key, value, first);
34                         int c = count + 1;
35                         if (c > threshold && tab.length < MAXIMUM_CAPACITY)
36                             rehash(node);
37                         else
38                             setEntryAt(tab, index, node);
39                         ++modCount;
40                         count = c;
41                         oldValue = null;
42                         break;
43                     }
44                 }
45             } finally {
46                 //释放锁
47                 unlock();
48             }
49             return oldValue;
50         }

jdk8中ConcurrentHashMap的put方法

1     public V put(K key, V value) {
2         return putVal(key, value, false);
3     }
 1     /** Implementation for put and putIfAbsent */
 2     final V putVal(K key, V value, boolean onlyIfAbsent) {
 3         //并发map中key、value均不能为null
 4         if (key == null || value == null) throw new NullPointerException();
 5         //spread和HashMap中的hash函数类似(h ^ (h >>> 16))&0x7fffffff,最后的与是为了消除负号
 6         int hash = spread(key.hashCode());
 7         int binCount = 0;
 8         //与jdk7不同,jdk8不在有segment,直接对每个桶加同步锁
 9         for (Node<K,V>[] tab = table;;) {
10             Node<K,V> f; int n, i, fh;
11             //table是懒加载的
12             if (tab == null || (n = tab.length) == 0)
13                 tab = initTable();
14             //如果桶中没有节点,则尝试使用cas添加
15             else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
16                 if (casTabAt(tab, i, null,
17                              new Node<K,V>(hash, key, value, null)))
18                     //第一个可以跳出循环的地方
19                     break;                   // no lock when adding to empty bin
20             }
21             //hash为MOVED说明该节点正在参与resize
22             else if ((fh = f.hash) == MOVED)
23                 tab = helpTransfer(tab, f);
24             else {//多数情况
25                 V oldVal = null;
26                 synchronized (f) {//对桶的头结点加同步锁,也就是对桶加锁,遍历桶中节点,进行插入或更新操作
27                     if (tabAt(tab, i) == f) {
28                         if (fh >= 0) {
29                             binCount = 1;
30                             for (Node<K,V> e = f;; ++binCount) {
31                                 K ek;
32                                 if (e.hash == hash &&
33                                     ((ek = e.key) == key ||
34                                      (ek != null && key.equals(ek)))) {
35                                     oldVal = e.val;
36                                     if (!onlyIfAbsent)
37                                         e.val = value;
38                                     break;
39                                 }
40                                 Node<K,V> pred = e;
41                                 if ((e = e.next) == null) {
42                                     pred.next = new Node<K,V>(hash, key,
43                                                               value, null);
44                                     break;
45                                 }
46                             }
47                         }
48                         else if (f instanceof TreeBin) {
49                             Node<K,V> p;
50                             binCount = 2;
51                             if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
52                                                            value)) != null) {
53                                 oldVal = p.val;
54                                 if (!onlyIfAbsent)
55                                     p.val = value;
56                             }
57                         }
58                     }
59                 }
60                 if (binCount != 0) {
61                     if (binCount >= TREEIFY_THRESHOLD)
62                         treeifyBin(tab, i);
63                     if (oldVal != null)
64                         return oldVal;
65                     //第二个可以跳出循环的地方
66                     break;
67                 }
68             }
69         }
70         addCount(1L, binCount);
71         return null;
72     }

其实看到jdk7的源码时我就在想,为什么要有segment呢?为什么不直接在桶上加锁呢?这是resize的缘故。如果直接在桶上加锁,resize时依旧需要在全局(table)加锁,那此时如果又有桶内节点的更新操作呢?table锁和桶锁并不互斥,这就出现并发问题了。在jdk8中取消了segment,采用直接在桶上加锁的方式,对于resize并发问题也有了相应的解决途径,就是采用复制。resize时会新建一个nextTable,在复制桶A中节点时会先将桶A加锁,解锁前会将原table中的桶首节点置为ForwardingNode(hash为MOVED),将原table中所有节点复制完成后,再table=nextTable。这里的关键就在于ForwardingNode,当线程看到当前桶的首节点为ForwardingNode时,线程不会继续更新操作,转而helpTransfer(帮助resize),resize结束后继续之前的更新操作。

jdk8和jdk7还有一个改变是,jdk7直接使用ReentrantLock,而jdk8转而使用cas+synchronized,cas肯定要比锁机制好,据说synchronized在jdk8中得到了优化,得空再看一下。

时间: 2024-12-20 19:46:04

java基础-ConcurrentHashMap的相关文章

Java基础】并发 - 多线程

Java基础]并发 - 多线程 分类: Java2014-05-03 23:56 275人阅读 评论(0) 收藏 举报 Java 目录(?)[+] 介绍 Java多线程 多线程任务执行 大多数并发应用程序时围绕执行任务(task)进行管理的:所谓任务就是抽象的,离散的工作单元. 围绕执行任务来管理应用程序时,第一步是要指明一个清晰的任务边界.大多数应用服务器程序都选择了下面这个自然的任务辩解:单独的客户请求: 任务时逻辑上的单元: 任务 Runnable 表示一个任务单元(java.lang)

java基础-java核心知识库

1.Spring.mvc的优势,原理,流程 2.Mybatis的原理优势 3.集合里面那些对象的原理 4.扩容原理,特别是map的底层 5.Hashmap.Hashtable和cocurrentHashMap的区别,要讲出它们各自的实现原理才行,比如Hashmap的扩容机制.cocurrentHashMap的段锁原理.多线程安全性. 6.几种造线程池的方法,区别 7.线程有哪几种状态,他们是如何转换的 8.Rpc原理,以及大致流程 9.Nio和netty的区别,为什么netty的性能高,nio,

深入Java基础(四)--哈希表(1)HashMap应用及源码详解

继续深入Java基础系列.今天是研究下哈希表,毕竟我们很多应用层的查找存储框架都是哈希作为它的根数据结构进行封装的嘛. 本系列: (1)深入Java基础(一)--基本数据类型及其包装类 (2)深入Java基础(二)--字符串家族 (3)深入Java基础(三)–集合(1)集合父类以及父接口源码及理解 (4)深入Java基础(三)–集合(2)ArrayList和其继承树源码解析以及其注意事项 文章结构:(1)哈希概述及HashMap应用:(2)HashMap源码分析:(3)再次总结关键点 一.哈希概

Java基础知识(二)

1,字符串 new String("abc")创建了几个对象? 一个或两个,如果常量池中原来有"abc",则只创建一个对象:如果常量池中原来没有字符串"abc",那么就会创建两个对象. String s="abc"; String s1="ab"+"c"; System.out.println(s==s1); 输出 true ,因为"ab"+"c"

Java基础面试题,JavaWeb面试题

java基础面试题 1.Java的基本类型(8个)每个基本类型所占位数与字节数byte 1字节 8位short 2字节 16位int 4字节 32位long 8字节 64位char 2字节 16位float 4字节 32位double 8字节 64位boolean 1字节 8位 2.Int与Integer区别Integer是int的包装类,int则是java的一种基本数据类型Integer变量必须实例化后才能使用,而int变量不需要Integer实际是对象的引用,当new一个Integer时,实

Java基础知识回顾之七 ----- 总结篇

前言 在之前Java基础知识回顾中,我们回顾了基础数据类型.修饰符和String.三大特性.集合.多线程和IO.本篇文章则对之前学过的知识进行总结.除了简单的复习之外,还会增加一些相应的理解. 基础数据类型 基本数据类型主要有: byte.short.int.long.float.double.char.boolean 它们可以分为三类: 数值类型:byte.short.int.long.float.double 字符类型:char 布尔型:boolean 其中byte是8位,short是16位

Java基础19:Java集合框架梳理

Java基础19:Java集合框架梳理 在编写java程序中,我们最常用的除了八种基本数据类型,String对象外还有一个集合类,在我们的的程序中到处充斥着集合类的身影! java中集合大家族的成员实在是太丰富了,有常用的ArrayList.HashMap.HashSet,也有不常用的Stack.Queue,有线程安全的Vector.HashTable,也有线程不安全的LinkedList.TreeMap等等! 上面的图展示了整个集合大家族的成员以及他们之间的关系.下面就上面的各个接口.基类做一

Java基础面试题总结(转)

目录 索引 Java基础知识篇 Java web基础知识总结 Java集合篇常见问题 Java基础知识篇 面向对象和面向过程的区别 面向过程: 优点:性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源;比如单片机.嵌入式开发.Linux/Unix等一般采用面向过程开发,性能是最重要的因素. 缺点:没有面向对象易维护.易复用.易扩展 面向对象: 优点:易维护.易复用.易扩展,由于面向对象有封装.继承.多态性的特性,可以设计出低耦合的系统,使系统更加灵活.更加易于维护 缺点:性能比面

使用java基础实现一个简陋的web服务器软件

使用java基础实现一个简陋的web服务器软件 1.写在前面 大学已经过了一年半了,从接触各种web服务器软件已经有一年多了,从大一上最开始折腾Windows电脑自带的IIS开始,上手了自己的第一个静态网站,从此开启了web方向学习的兴趣.到现在,从陪伴了javaweb阶段的Tomcat走来,也陆续接触了jetty,Nginx等web服务器软件.但是,这些web服务器的软件也一直都是开箱即用,从未探究过其背后的原理.今天,尽量用最简单的java代码,实现一个最简陋的web服务器软件,揭开web服