java之 HashMap、HashTable、ConcurrentHashMap介绍

java并发场景:当多个线程访问同一个HashMap进行新增修改就会造成线程不安全,因为HashMap没有被任何锁修饰(为什么要修饰请查看我的博文volatile关键字)
HashMap: 是一个table数组,table数组的元素为Entry节点,采用key计算出Hash值存放值( int hash = hash(key);),并且没有用到Volatile,final,CAS以及Synchronized或者Lock等锁的使用,所以在并发场景中会造成线程不安全。
Entry节点对象有四个属性: Key(键对象), value(值对象), hash(键对象的hash值), entry(指向链表中下一个Entry对象,可为Null,表示当前Entry对象在链表尾部)
HashTable: 数据结构和HashMap是一样的,在put,get,remove等所有方法上都被Synchronized修饰,所以在并发场景中线程是安全的,但是每个线程只会获得一把锁,就会造成性能下降,所以不适合做高并发场景使用。
ConcurrentHashMap:  默认情况下是有16个Segment 数组组成,每一个Segment是有一个Entry数组,数组上每个节点是有HashEntry组成,ConcurrentHashMap采用key的hashCode然后spread获得存放地址( int hash = spread(key.hashCode());)
采用了分段锁的设计,只有在同一个分段内才存在竞态关系,不同的分段锁之间没有锁竞争。相比于对整个Map加锁的设计,分段锁大大的提高了高并发环境下的处理能力。但同时,由于不是对整个Map加锁,导致一些需要扫描整个Map的方法(如size(), containsValue())需要使用特殊的实现,另外一些方法(如clear())甚至放弃了对一致性的要求(ConcurrentHashMap是弱一致性的
ConcurrentHashMap中的分段锁称为Segment,它即类似于HashMap(JDK7与JDK8中HashMap的实现)的结构,即内部拥有一个Entry数组,数组中的每个元素又是一个链表;同时又是一个ReentrantLock(Segment继承了ReentrantLock)。ConcurrentHashMap中的HashEntry相对于HashMap中的Entry有一定的差异性:HashEntry中的value以及next都被volatile修饰,这样在多线程读写过程中能够保持它们的可见性
HashEntry对象代码:
static final class HashEntry<K,V> {
        final int hash;
        final K key;
        volatile V value;
        volatile HashEntry<K,V> next;

jdk版本历史:在jdk1.8之前,ConcurrentHashMap采用 ReentrantLock进行分段锁,在jdk1.8采用了全新的CAS机制,也就是乐观锁,并发效率明显提升。并且底层依然由“数组”+链表+红黑树的方式思想
并发度优缺点:如果分段锁个数设置的过小,会带来严重的锁竞争问题;如果并发度设置的过大,原本位于同一个Segment内的访问会扩散到不同的Segment中,CPU cache命中率会下降,从而引起程序性能下降



猜你喜欢

转载自blog.csdn.net/qq_39291929/article/details/80928771