ConcurrentHashMap
实现原理
HashMap在多线程并发put时,会导致Entry链表形成环形数据结构,一旦形成环形数据结构,Entry的next的节点就永远不会为空,就会产生死循环获取Entry。
锁分段:将数据分成一段一段,每一段数据分配一把锁,当一个线程占用锁访问其中一个段数据的生时候,其他段的数据也能被其他线程访问。
ConcurrentHashMap包含一个Segment数组,Segment是一种可重入锁,Segment结构类似于HashMap,是一种数组加链表的结构,一个Segment包含一个HashEntry数组。每个HashEntry是一个链表结构。当对HashEntry数组的数据进行修改时,必须首先获得与它对应的Segment锁。
基本使用
get操作
get操作高效不加锁,是因为get方法里使用到的共享变量都定义成了volatile类型,包括统计当前Segment大小的count字段和存储值的HashEntry的value。根据java内存模型的happen before原则,保证对volatile字段读总能可见最后的写入。使得两个线程在同时写和读volatile变量,get操作也能拿到最新的值。这是valtile替换锁的经典应用场景。
put操作
1、判断是否需要扩容
HashEntry数组是否超过容量
2、如何扩容
只对HashEntry对应的Segment扩容到原容量的两倍,再将原数据再散列到新容器中。
size操作
要统计整个ConcurrentHashMap的元素大小,因为Segment的变量count是全局volatile类型,故可以相加得到统计值。但是再统计过程中如果不对put,remove,clean等修改操作加锁,可能会造成统计前后count值发生变化。如果加锁又影响效率。为此ConcurrentHashMap的做法是引入modCount变量。在put,remove,clean操作中都会修改modCount变量值。从而只需要在统计前后比较modCount值是否发生变化,如果连续尝试两次统计前后modCount都发生变化,则采用加锁的方式统计。
非阻塞队列ConcurrentLinkedQueue
实现原理
由head节点和tail节点组成,每个节点由节点元素item和指向下一个节点的next引用组成。
基本使用
入队列
通过tail定位到尾节点,然后通过cas操作将节点设置成尾节点的next节点,这里如果失败会重试。注意通过tail并不能一次定位到尾节点,原因如下。
添加元素到队尾,但并不是每次都更新tail到队尾节点,而是间隔hops次更新一次到尾结点,hops参数默认为1,这么做的原因在于设置tail的动作是cas操作,通过此方法能减少cas的操作次数,从而提高效率。
出队列
返回队列的第一个有元素的节点,并清空该节点对元素的引用。同tail一样,head节点也并不是每次都指向队列的第一个元素,同样是hops次数指向一次队列第一个有值元素,否则间隙情况下head指向的元素为空,其next引用指向的下一个元素才是真正有值需要返回的队列头元素。