JDK1.8下ConcurrentHashMap的一些理解(一)
在JDK1.8里面,ConcurrentHashMap
在put方法里面已经将分段锁移除了,转而是CAS锁和synchronized
ConcurrentHashMap
是Java里面同时兼顾性能和线程安全的一个键值对集合,同属于键值对的集合还有HashTable
以及HashMap
,HashTable
是一个线程安全的类,因为它的所有public
方法都被synchronized
修饰,这样就导致了一个问题,就是效率太低。
虽然HashMap
在JDK1.8
的并发场景下触发扩容时不会出现成环了,但是会出现数据丢失的情况。
所以如果需要在多线程的情况下(多读少写))使用Map集合的话,ConcurrentHashMap
是一个不错的选择。
ConcurrentHashMap
在JDK1.8的时候将put()方法中的分段锁Segment
移除,转而采用一种CAS
锁和synchronized
来实现插入方法的线程安全。
如下代码:
1 | /** Implementation for put and putIfAbsent */ |
可以看到在JDK1.8
里面,ConcurrentHashMap
是直接采用数组
+链表
+红黑树
来实现,时间复杂度在O(1)和O(n)之间,如果链表转化为红黑树了,那么就是O(1)到O(nlogn)。
在这里值得一提的是,ConcurrentHashMap
会判断tabAt(tab, i = (n - 1) & hash)
是不是 null,是的话就直接采用CAS
进行插入,而如果不为空的话,则是synchronized
锁住当前Node
的首节点,这是因为当该Node
不为空的时候,证明了此时出现了Hash
碰撞,就会涉及到链表
的尾节点新增或者红黑树
的节点新增以及红黑树
的平衡,这些操作自然都是非原子性的。
从而导致无法使用CAS
,当Node
的当前下标为null的时候,由于只是涉及数组的新增,所以用CAS
即可。
因为CAS是一种基于版本控制的方式来实现,而碰撞之后的操作太多,所以直接用
synchronized
比较合适。
ConcurrentHashMap在迭代时和HashMap的区别
当一个集合在迭代的时候如果动态的添加或者删除元素,那么就会抛出Concurrentmodificationexception
,但是在ConcurrentHashMap
里面却不会,例如如下代码:
1 | public static void main(String[] args) { |
而当你把ConcurrentHashMap
换成HashMap
的时候,控制台就会抛出一个异常:
1 | Exception in thread "main" a |
原因在于ConcurrentHashMap
的next
方法并不会去检查modCount
和expectedModCount
,但是会检查下一个节点是不是为空
1 | if ((p = next) == null) |
当我们进行remove的时候,ConcurrentHashMap
会直接通过修改指针的方式来进行移除操作,同样的,也会锁住数组
的头节点直至移除结束,所以在同一个时刻,只会有一个线程对当前数组下标的所有节点
进行操作。
但是在HashMap
里面,next
方法会进行一个check,而remove操作会修改modCount
,导致modCount
和expectedModCount
不相等,所以就会导致ConcurrentModificationException
稍微修改下代码:
1 | public static void main(String[] args) { |
并发下的处理
由于每一个Node
的首节点都会被synchronized
修饰,从而将一个元素的新增转化为一个原子操作,同时Node
的value
和next
都是由volatile
关键字进行修饰,从而可以保证可见性。
JDK1.8下ConcurrentHashMap的一些理解(一)
https://somersames.github.io/2019/05/13/JDK1.8下ConcurrentHashMap的一些理解(一)/