没摸鱼的日子都是在好好学习嘿嘿---并发容器ConcurrentHashMap

37 阅读5分钟

---------------------------------并发容器ConcurrentHashMap1.7---------------------------------------- hash算法:

将任意长度的输入通过算法得到固定长度的输出,即为散列值
是一种压缩映射,直接取余操作
hash冲突的解决:链地址法、开发地址法

hashTable的线程安全的吗?

是的,hashTable的线程安全是基于Synchronized关键字来实现的,同一时间只有一个线程能够操作数据,所以是线程安全的
串行化的设计理念 ->高并发情况下肯定会被舍弃的,所以有了ConcurrentHashMap
    

ConcurrentHashMap在JDK1.7

数据结构
	Segemnt数组 + HashEntry<K , Y>数组:
	Segment数组:
		ConcurrentHashMap内部维护了一个Segment数组,这个数组类似于HashMap的桶(bucket)数组。
		每个Segment都是一个可重入锁(ReentrantLock),并且它自身也是一个简化版的HashMap。	
	HashEntry数组:
		每个Segment内部包含了一个HashEntry数组,用于存储键值对。
		HashEntry是ConcurrentHashMap中的节点类型,它包含了键、值以及指向下一个元素的指针,形成了链表结构。
		如果多个键映射到同一个索引位置,则它们会形成一个链表。
                    
构造方法的三个参数:初始容量,负载因子,并发等级
	如果initialCapacity小于concurrentLevel,那么Segment数组的实际长度将会是大于或等于concurrentLevel的最小的2的幂次
            
getkey)方法   <定位:通过hash值和数组长度-1做按位&>
	先获取hashcode,然后再hash获取哈希值 <两次hash减小碰撞,分布更加均匀>
	通过哈希值定位数据在哪一个分段上
	获取那个分段上的hashEntrty数组,然后判断是在哪一个链表上
	遍历链表获取value
            
put(key,value)方法
	根据key定位所属分段
	对分段上锁
	定位table,对链表扫描,有则覆盖,无则加到头节点
            
各种操作都跟2的n次方有关系,有什么好处
	1.高效的模运算
		取模可以用算数&,运算会非常快
	2.动态调整容量
		新的容量也很容易调整为下一个更大的2的幂次方,可以利用位操作来快速确定元素的新位置,而无需复杂的计算。原来数组中的有部分数据在新数组中的位置是不变的,大大提高了扩容效率
	3.均匀的散列分布
		当容量为2的幂次方时,低位的哈希值位将决定元素的存储位置,这有助于确保即使高几位的哈希值相同,元素也能均匀分布在不同的桶中。

size()方法
	计算ConcurrentHashMap中所有键值对的个数,通过两次计算键值对个数判断是否有线程正在操作HashEntrty(两次计算不一致就是有线程在操作),当两次结果不一致就会给所有分段加锁进行统计,统计完后释放<会降低效率>

--------------------------------------------------------------1.8--------------------------------------------------- ConcurrentHashMap在JDK1.8

数据结构:
	数组 + 链表 + 红黑树
	锁粒度降低了:1.7及以前锁的是分段,而分段下面是HashEntry数组相当于hashmap,所以以前锁的是一个hashmap,在新的实现中,锁的粒度从Segment级别下降到了链表或红黑树的节点级别。具体而言,当进行写操作时,ConcurrentHashMap会使用CAS操作来尝试更新节点,仅在CAS失败(即有其他线程正在修改同一节点)时才使用锁,而且锁住的是链表的头节点或者红黑树的根节点,而不是整个HashMap。

put()方法
	检查table是否存在,否则初始化table
	检查table对应位置是否为null,否则填充新建节点检查是否需要扩容
	检查table节点key是否相同,是则直接返回value锁定table的节点:链表操作/红黑树操作
	计数加1
get()方法
	spread(hash(key)):定位节点在table中的位置检查table的节点与给定key是否相同,是则返回value在树中寻找;在链表中寻找;
	

---------------------------------------------------------对比------------------------------ConcurrentHashMap1.7与1.8的区别

1. 锁粒度: 1.7之前用的是分段锁,锁粒度太大了,1.8之后使用对象锁,锁的是链表的首节点,和红黑树的根节点---------锁粒度降低了:1
2. 并发度: 1.7之前用的分段锁,且分段数在初始化的时候就固定了,所以1.7之前的并发度最大为16
3. 数据结构: 1.7之前用的 分段 数组 链表 而 1.8之后用的数组 链表 红黑树,查找速率更快,当链表长度>=8就会树化,而当红黑树节点个数<=6就会退化为链表
4. 下标定位: key的hashCode再散列然后对长度取模,1.71.8的再哈希实现不一样
5.  扩容: 1.7的数据结构时一个segment数组,每个segment下面有一个hashEntry数组,扩容就是扩容HashEntry数组,由于分段锁的锁粒度大,每个segment下面的hashmap的扩容是有一个线程完成,当一个segment的负载因子超过了阈值的时候就会触发扩容,会新建一个hashEntry数组(长度扩大一倍),然后将之前里面的节点都重新put进新的hashentry数组。
1.8的数据结构取消了segment数组,并发控制由 volatile cas操作 synchronized来处理,他有一个扩容阈值,当hashmap达到阈值的时候,就会新建一个扩大一倍的数组,当其他线程想要put元素的时候,发现此时正在进行扩容操作,就会通过synchronized锁 首节点 或 根节点,也往新数组中put元素。
    
    

注:get put都是大致流程,详细的得看源码,1.8源码贼长