您的位置:首页 > 其它

concurrenthashmap

2019-07-06 14:49 225 查看

concurrenthashmap.............................................................................................................1
1. 内容..............................................................................................................................2
2. 数据结构......................................................................................................................2
3. ReentrantLock与volatile结合......................................................................................3
4. hash求值......................................................................................................................4
5. size()..............................................................................................................................4
6. 总结..............................................................................................................................5
7. 扩容..............................................................................................................................5

1. 内容
Map的创建:ConcurrentHashMap()
往Map中添加键值对:即put(Object key, Object value)方法
获取Map中的单个对象:即get(Object key)方法
删除Map中的对象:即remove(Object key)方法
判断对象是否存在于Map中:containsKey(Object key)
遍历Map中的对象:即keySet().iterator(),在实际中更常用的是增强型的for循环去做遍历
2. 数据结构
个指定个数的Segment数组,数组中的每一个元素Segment相当于一个HashTable。
/**
* 一个特殊的HashTable
*/
static final class Segment<K, V> extends ReentrantLock implements
Serializable {
/**
* Segment中的HashEntry节点 类比HashMap中的Entry节点
*/
static final class HashEntry<K, V> {
final K key;// 键
final int hash;//hash值
volatile V value;// 实现线程可见性
final HashEntry<K, V> next;// 下一个HashEntry
/**
* 创建ConcurrentHashMap
*/
public ConcurrentHashMap() {
this(DEFAULT_INITIAL_CAPACITY, // 16
DEFAULT_LOAD_FACTOR, // 0.75f
DEFAULT_CONCURRENCY_LEVEL);// 16
}
五点注意:
传入的concurrencyLevel只是用于计算Segment数组的大小(可以传入不是2的几次方的数,但是根据下边的计算
,最终segment数组的大小ssize将是2的几次方的数),并非真正的Segment数组的大小
传入的initialCapacity只是用于计算Segment数组中的每一个segment的HashEntry[]的容量,
但是并不是每一个segment的HashEntry[]的容量,而每一个HashEntry[]的容量不是2的几次方
非常值得注意的是,在默认情况下,创建出的HashEntry[]数组的容量为1,并不是传入的initialCapacity(16),证实
了上一点;而每一个Segment的扩容因子threshold,一开始算出来是0,即开始put第一个元素就要扩容,不太理解J
DK为什么这样做。
想要在初始化时扩大HashEntry[]的容量,可以指定initialCapacity参数,且指定时最好指定为2的几次方的一个数
,这样的话,在代码执行中可能会少执行一句"c++",具体参看三参构造器的注释
对于Concurrenthashmap的扩容而言,只会扩当前的Segment,而不是整个Concurrenthashmap中的所有Segment都

3. ReentrantLock与volatile结合
onlyIfAbsent--
>false如果有旧值存在,新值覆盖旧值,返回旧值;true如果有旧值存在,则直接返回旧值,相当于不添加元素
ReentrantLock的用法:必须手工释放锁。可实现Synchronized的效果,原子性。
volatile需要配合锁去使用才能实现原子性,否则在多线程操作的情况下依然不够用,在程序中,count变量(当前
Segment中的keyvalue
对个数)通过volatile修饰,实现内存可见性(关于内存可见性以后会仔细去记录,这里列出大概的一个流程)
在有锁保证了原子性的情况下
当我们读取count变量的时候,会强制从主内存中读取count的最新值
当我们对count变量进行赋值之后,会强制将最新的count值刷到主内存中去
通过以上两点,我们可以保证在高并发的情况下,执行这段流程的线程可以读取到最新值
4. hash求值
注释很重要,一定要看
注释已经写明了详细流程,这里说一下大致流程:
根据key获取hash值
根据hash值找到相应的Segment
根据hash值找出Segment中的哪一个HashEntry[index]
遍历整个HashEntry[index]链表,找出hash和key与给定参数相等的HashEntry,例如e
如没找到e,返回null
如找到e,获取e.value
如果e.value!=null,直接返回
如果e.value==null,则先加锁,等并发的put操作将value设置成功后,再返回value值
对于get操作而言,基本没有锁,只有当找到了e且e.value等于null,有可能是当下的这个HashEntry刚刚被创建,val
ue属性还没有设置成功,这时候我们读到是该HashEntry的value的默认值null,所以这里加锁,等待put结束后,返
回value值
据说,上边这一点还没有发生过
5. size()
在不加锁的情况下遍历所有Segment,读取每个Segment的count和modCount,并进行统计;
完毕后,再遍历一遍所有Segment,比较modCount,是否发生了变化,若发生了变化,则再重复如上动作两次;
若三次后,还未成功,遍历所有Segment,分别加锁(即建立全局锁),然后计算,最后释放所有锁。
注:以如上的方式,大部分情况下, 4000 不需要加锁就可以获取size()
6. 总结
总结:
数据结构:一个指定个数的Segment数组,数组中的每一个元素Segment相当于一个HashTable
加锁情况(分段锁):
put
get中找到了hash与key都与指定参数相同的HashEntry,但是value==null的情况
remove
size():三次尝试后,还未成功,遍历所有Segment,分别加锁(即建立全局锁)
7. 扩容
\old),如果超过阀值,数组进行扩容。值得一提的是,Segment的扩容判断比HashMap更恰当,因为HashMa
p是在插入元素后判断元素是否已经到达容量的,如果到达了就进行扩容,但是很有可能扩容之后没有新元素
插入,这时HashMap就进行了一次无效的扩容。
Concurrenthashmap如何扩容。扩容的时候首先会创建一个两倍于原容量的数组,然后将原数组里的元素进行再h
ash后插入到新的数组里。为了高效ConcurrentHashMap不会对整个容器进行扩容,而只对某个segment进行扩容

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: