论坛首页 Java企业应用论坛

ConcurrentHashMap之实现细节

浏览 187176 次
该帖已经被评为良好帖
作者 正文
   发表时间:2009-03-10  
marlonyao 写道
dlovek 写道
恩,我有看到在Segment的删除/添加方法都是先修改了modCount然后修改了volatile的count值。
在containsValue方法中,“内层的第一个for循环,里面有语句int c = segments[i].count; 但是c却从来没有被使用过,即使如此,编译器也不能做优化将这条语句去掉,因为存在对volatile变量count的读取,这条语句存在的唯一目的就是保证segments[i].modCount读取到几乎最新的值”,这个就不理解了。


见我前面的回复,如果不先读count,就有可能读到modCount很久以前的陈旧值。




volatile 不能100%保证读到的是最新的数值,也可说volatile是轻量级别的sychronized。
0 请登录后投票
   发表时间:2009-03-10  
看完文章后有个感觉 ConcurrentHashMap适合在高读并发低写并发的情况下使用 如果在高写并发的情况下使用 连get的安全性都不能保障 是这个意思吗? 写并发的负载到什么程度ConcurrentHashMap才会不安全?
1 请登录后投票
   发表时间:2009-03-10  
unsid 写道
final Segment<K,V>[] segments;

final Segment<K,V> segmentFor(int hash) {  
    return segments[(hash >>> segmentShift) & segmentMask];  


public V remove(Object key) {  
    hash = hash(key.hashCode());  
    return segmentFor(hash).remove(key, hash, null);  
}

我对此处的segments不理解,这里的segments数组是干吗的?我起初还以为相当于HashMap中的Entity[],仔细一看,每个segment里面都有一个HashEntry[],这个应当是真正哈西表按照楼主说的,每个用户操作在不同的segment上不同segment有不同的锁,那让segment[]充当Entity[],而每个segment里仅有一个HashEntry链表,这样只有两个用户删除哈西值相同的元素时才需要同步.

为虾米先要segmentFor(hash)而后在remove方法里又哈西算一次坐标?       
  int index = hash & (tab.length - 1);  
  HashEntry<K,V> first = tab[index]; 



每个Segment包含多个hash链。
0 请登录后投票
   发表时间:2009-03-10  
mercyblitz 写道
marlonyao 写道
dlovek 写道
恩,我有看到在Segment的删除/添加方法都是先修改了modCount然后修改了volatile的count值。
在containsValue方法中,“内层的第一个for循环,里面有语句int c = segments[i].count; 但是c却从来没有被使用过,即使如此,编译器也不能做优化将这条语句去掉,因为存在对volatile变量count的读取,这条语句存在的唯一目的就是保证segments[i].modCount读取到几乎最新的值”,这个就不理解了。


见我前面的回复,如果不先读count,就有可能读到modCount很久以前的陈旧值。




volatile 不能100%保证读到的是最新的数值,也可说volatile是轻量级别的sychronized。


volatile保证100%读取到最新的数据,对这里来说,它保证100%读取到count的最新值,但是对非volatile变量就不一样了。
0 请登录后投票
   发表时间:2009-03-10  
kevinwong 写道
看完文章后有个感觉 ConcurrentHashMap适合在高读并发低写并发的情况下使用 如果在高写并发的情况下使用 连get的安全性都不能保障 是这个意思吗? 写并发的负载到什么程度ConcurrentHashMap才会不安全?



get总是安全的。
0 请登录后投票
   发表时间:2009-03-10  
marlonyao 写道
kevinwong 写道
看完文章后有个感觉 ConcurrentHashMap适合在高读并发低写并发的情况下使用 如果在高写并发的情况下使用 连get的安全性都不能保障 是这个意思吗? 写并发的负载到什么程度ConcurrentHashMap才会不安全?



get总是安全的。


引用
get操作不需要锁。第一步是访问count变量,这是一个volatile变量,由于所有的修改操作在进行结构修改时都会在最后一步写count变量,通过这种机制保证get操作能够得到几乎最新的结构更新。对于非结构更新,也就是结点值的改变,由于HashEntry的value变量是volatile的,也能保证读取到最新的值。接下来就是对hash链进行遍历找到要获取的结点,如果没有找到,直接访回null。对hash链进行遍历不需要加锁的原因在于链指针next是final的。但是头指针却不是final的,这是通过getFirst(hash)方法返回,也就是存在table数组中的值。这使得getFirst(hash)可能返回过时的头结点,例如,当执行get方法时,刚执行完getFirst(hash)之后,另一个线程执行了删除操作并更新头结点,这就导致get方法中返回的头结点不是最新的。这是可以允许,通过对count变量的协调机制,get能读取到几乎最新的数据,虽然可能不是最新的。要得到最新的数据,只有采用完全的同步。


这是原话 而且新put的entity都是放到节点头的 那说明在一个线程内put的entity在另一个线程内不保证能get出来 这是我的理解
0 请登录后投票
   发表时间:2009-03-10  
kevinwong 写道

marlonyao 写道kevinwong 写道看完文章后有个感觉 ConcurrentHashMap适合在高读并发低写并发的情况下使用 如果在高写并发的情况下使用 连get的安全性都不能保障 是这个意思吗? 写并发的负载到什么程度ConcurrentHashMap才会不安全?


get总是安全的。

引用 get操作不需要锁。第一步是访问count变量,这是一个volatile变量,由于所有的修改操作在进行结构修改时都会在最后一步写count变量,通过这种机制保证get操作能够得到几乎最新的结构更新。对于非结构更新,也就是结点值的改变,由于HashEntry的value变量是volatile的,也能保证读取到最新的值。接下来就是对hash链进行遍历找到要获取的结点,如果没有找到,直接访回null。对hash链进行遍历不需要加锁的原因在于链指针next是final的。但是头指针却不是final的,这是通过getFirst(hash)方法返回,也就是存在table数组中的值。这使得getFirst(hash)可能返回过时的头结点,例如,当执行get方法时,刚执行完getFirst(hash)之后,另一个线程执行了删除操作并更新头结点,这就导致get方法中返回的头结点不是最新的。这是可以允许,通过对count变量的协调机制,get能读取到几乎最新的数据,虽然可能不是最新的。要得到最新的数据,只有采用完全的同步。

这是原话 而且新put的entity都是放到节点头的 那说明在一个线程内put的entity在另一个线程内不保证能get出来 这是我的理解


是的。但是get能够看到调用get方法之前所有已经完成的put(因为如果发生结构性改变它会写count),虽然可能看不到正在进行的put操作(还没有写count)。这是一个很小的时间窗口,这不可避免,因为get没有同步。这并不是说get不安全,即使get操作时,其它线程在put,get也能看到一致的hash链(与HashMap相比较)。在HashMap中,假设put方法是同步的,get没有同步,get有可能看到实际上从不存在hash链。

get方法总是返回最近put过的数据,这句话说得很别扭,却是很重要的。假设其它线程先后调用了put("a", 1),put("a", 2),put("a", 3),这里能够使用“先后”,是因为put方法是同步的,put方法是排序的,每一个时刻只能够发生一个put方法。接着一个线程调用get("a")方法,并且另一个线程正在调用put("a", 4)方法,那么get("a")会返回3或者4,完全取决于("a")访问结点的值(读value)和put("a", 4)写结点的值(写value)哪个先发生(读volatile变量和写volatile总有个先后顺序,读写普通变量就不一定了,它们可能同时发生),这时put不会发生结构性更新。如果在调用get("a")时,另一个线程正在调用remove("a"),get方法要么返回3,要么返回null,这取决于get方法中getFirst(hash)返回的到底是remove("a")之前还是之后的头结点,而绝不会返回1, 2或任何其它的值。
0 请登录后投票
   发表时间:2009-03-11  
marlonyao 写道
kevinwong 写道

marlonyao 写道kevinwong 写道看完文章后有个感觉 ConcurrentHashMap适合在高读并发低写并发的情况下使用 如果在高写并发的情况下使用 连get的安全性都不能保障 是这个意思吗? 写并发的负载到什么程度ConcurrentHashMap才会不安全?


get总是安全的。

引用 get操作不需要锁。第一步是访问count变量,这是一个volatile变量,由于所有的修改操作在进行结构修改时都会在最后一步写count变量,通过这种机制保证get操作能够得到几乎最新的结构更新。对于非结构更新,也就是结点值的改变,由于HashEntry的value变量是volatile的,也能保证读取到最新的值。接下来就是对hash链进行遍历找到要获取的结点,如果没有找到,直接访回null。对hash链进行遍历不需要加锁的原因在于链指针next是final的。但是头指针却不是final的,这是通过getFirst(hash)方法返回,也就是存在table数组中的值。这使得getFirst(hash)可能返回过时的头结点,例如,当执行get方法时,刚执行完getFirst(hash)之后,另一个线程执行了删除操作并更新头结点,这就导致get方法中返回的头结点不是最新的。这是可以允许,通过对count变量的协调机制,get能读取到几乎最新的数据,虽然可能不是最新的。要得到最新的数据,只有采用完全的同步。

这是原话 而且新put的entity都是放到节点头的 那说明在一个线程内put的entity在另一个线程内不保证能get出来 这是我的理解


是的。但是get能够看到调用get方法之前所有已经完成的put(因为如果发生结构性改变它会写count),虽然可能看不到正在进行的put操作(还没有写count)。这是一个很小的时间窗口,这不可避免,因为get没有同步。这并不是说get不安全,即使get操作时,其它线程在put,get也能看到一致的hash链(与HashMap相比较)。在HashMap中,假设put方法是同步的,get没有同步,get有可能看到实际上从不存在hash链。

get方法总是返回最近put过的数据,这句话说得很别扭,却是很重要的。假设其它线程先后调用了put("a", 1),put("a", 2),put("a", 3),这里能够使用“先后”,是因为put方法是同步的,put方法是排序的,每一个时刻只能够发生一个put方法。接着一个线程调用get("a")方法,并且另一个线程正在调用put("a", 4)方法,那么get("a")会返回3或者4,完全取决于("a")访问结点的值(读value)和put("a", 4)写结点的值(写value)哪个先发生(读volatile变量和写volatile总有个先后顺序,读写普通变量就不一定了,它们可能同时发生),这时put不会发生结构性更新。如果在调用get("a")时,另一个线程正在调用remove("a"),get方法要么返回3,要么返回null,这取决于get方法中getFirst(hash)返回的到底是remove("a")之前还是之后的头结点,而绝不会返回1, 2或任何其它的值。


受教了 谢谢:)
0 请登录后投票
   发表时间:2009-03-11   最后修改:2009-03-11
原来的HashTable一锁就锁掉了整个map,现在这个锁只锁了一个segments,锁的范围小了,很多情况下可以不所解决问题。。。于是效率提高了。。。
0 请登录后投票
   发表时间:2009-03-11  
为什么删除的时候要吧原来的节点复制一次,不能直接删除吗?
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics