Java容器来一发(五)ConcurrentHashMap

1、ConcurrentHashMap简介

ConcurrentHashMap采用了分段锁的设计,只有在同一个分段内才存在竞态关系,不同的分段锁之间没有锁竞争,因此相对于HashMap效率更高。也就是说ConcurrentHashMap可以做到读取数据不加锁,并且其内部的结构可以让其在进行写操作的时候能够将锁的粒度保持地尽量地小,不用对整个ConcurrentHashMap加锁。

ConcurrentHashMap内部分成了很多段,即segments,每个段相当于一个HashTable。读操作不加锁,写操作只锁一个segment,所以效率较高。

2、get方法

get操作不需要锁,除非读到的值是空的才会加锁重读,原因是它的get方法里将要使用的共享变量都定义成volatile,而get操作里只需要读不需要写共享变量count和value。

get方法分三步:

第一步,访问count变量,它是volatile变量,对于增删操作,由于其最终都会写count 变量,因此访问count变量可以拿到准确值;对于非增删操作,也就是结点值的改变,由于HashEntry的value变量是 volatile的,也能保证读取到最新的值。

第二步,getFirst找到头指针,对hash链进行遍历找到要获取的结点,如果没有找到,直接访回null。对hash链进行遍历不需要加锁的原因在于链指针next是final的。但getFirst(hash)可能返回过时的头结点,这是可以接受的。如果要得到最新的数据,只有采用完全的同步。

第三步,如果找到了所需结点,判断它的值如果非空就直接返回,否则在有锁的状态下再读一次。

3、put方法

put是在持有段锁的情况下执行的,由于put方法里需要对共享变量(count)进行写入操作,所以为了线程安全,在操作共享变量时必须得加锁。put方法首先定位到Segment,然后在Segment里进行插入操作,插入操作分两步,第一步判断是否需要对Segment里的HashEntry数组进行扩容,第二步定位添加元素的位置然后放在HashEntry数组里。

4、size方法

执行size方法时,如果为了保证线程安全,将put,remove等方法全部锁住,显然会非常低效。ConcurrentHashMap的思路是:因为在累加各Segment的count过程中之前累加过的count发生变化的几率比较小,所以先尝试两次通过不锁住Segment的方式来统计各个Segment大小,如果统计的过程中,容器的count发生了变化,则再采用加锁的方式来统计所有Segment的大小。如何判断统计的时候count是否发生了变化?ConcurrentHashMap中有一个全局的modCount变量,所有的增删变更都会修改这个变量,通过它可以判断。

参考资料:

https://blog.csdn.net/cj2580/article/details/53148165

猜你喜欢

转载自blog.csdn.net/ss1300460973/article/details/85631102