关于 ConcurrentHashMap put 和 get 方法实现的疑惑

meran 2011-08-19 05:06:18
先给出源码

V get(Object key, int hash) {
if (count != 0) { // read-volatile
HashEntry<K,V> e = getFirst(hash);
while (e != null) {
if (e.hash == hash && key.equals(e.key)) {
V v = e.value;
if (v != null)
return v;
return readValueUnderLock(e); // recheck
}
e = e.next;
}
}
return null;
}



V put(K key, int hash, V value, boolean onlyIfAbsent) {
lock();
try {
int c = count;
if (c++ > threshold) // ensure capacity
rehash();
HashEntry<K,V>[] tab = table;
int index = hash & (tab.length - 1);
HashEntry<K,V> first = tab[index];
HashEntry<K,V> e = first;
while (e != null && (e.hash != hash || !key.equals(e.key)))
e = e.next;

V oldValue;
if (e != null) {
oldValue = e.value;
if (!onlyIfAbsent)
e.value = value;
}
else {
oldValue = null;
++modCount;
tab[index] = new HashEntry<K,V>(key, hash, first, value);
count = c; // write-volatile
}
return oldValue;
} finally {
unlock();
}
}



网上对get 的分析大概是这样

取出key对应的value的值,如果拿出的value的值是null,则可能这个key,value对正在put的过程中,如果出现这种情况,那么就加锁来保证取出的value是完整的,如果不是null,则直接返回value。

比如我们 表中已经有了 一个< "a","bc">对
我现在调用 put插入 <"a","cd"> , 在 "bc" 被赋值为 "cd" 之前, get("a") 方法被调用 ,get拿到了"bc"并返回,此时 put
才把Value "bc" 赋值为 "cd" 。

也就是说 ConcurrentHashMap 并不保证 put get 的一致性 , 是不是说 get 拿到是脏数据???
后面对于value 为 null 的加锁调用,只是为了保证数据的完整性?



还是我有误解?

...全文
1351 16 打赏 收藏 转发到动态 举报
写回复
用AI写文章
16 条回复
切换为时间正序
请发表友善的回复…
发表回复
meran 2011-08-19
  • 打赏
  • 举报
回复
疑问已经解决 我想明白了。。。 get 方法返回的是对volatile value 的引用,所以在 put 后value
指向了其他对象的引用,那么 也会被 get察觉。
meran 2011-08-19
  • 打赏
  • 举报
回复
[Quote=引用 10 楼 zhao251021539 的回复:]
Java code


V get(Object key, int hash) {
if (count != 0) { // read-volatile
HashEntry<K,V> e = getFirst(hash);
while (e != null) {
……
[/Quote]


能对于我上面的疑问给个解释么?
BadPattern 2011-08-19
  • 打赏
  • 举报
回复
volatile的作用:
1,内存同步。
2,以原子的方式来进行long,double类型的指定。
volatile 可以保证变量的视图完整性, 各CPU在使用该变量的时候不再 缓存中读,而是直接在内存中读。。。
这个意思跟java内存模型有关,每个线程都有自己的工作存储器,它们分别从主存储器取数据并加以运算,运算之后如果不加以内存的同步,线程就看不到其他线程修改的结果啦。

简单的来说:
synchronized 提供了内存同步+互斥
volatile只提供了内存同步,它的适用场景很少,仅在少数几种模式,也建议你看看IBM developerworks的文章:http://www.ibm.com/developerworks/cn/java/j-jtp06197.html
和java多线程内存模型相关资料
  • 打赏
  • 举报
回复
ConcurrentHashMap 采用的是一种称为 stripping lock(分离锁)的线程同步锁策略。
  • 打赏
  • 举报
回复
要弄明白 volatile 的话,先弄明白 JVM 线程栈、共享数据空间的关系
meran 2011-08-19
  • 打赏
  • 举报
回复
HashEntry<K,V> value 域被设置为 volatile

对于 volatile 的语意我一直不很清楚, 说 volatile 可以保证变量的视图完整性, 各CPU在使用该变量的时候不再 缓存中读,而是直接在内存中读。。。

4. Happens-before法则:(Java 内存模型)
如果动作B要看到动作A的执行结果(无论A/B是否在同一个线程里面执行),那么A/B就需要满足happens-before关系。

Happens-before的几个规则:

•Program order rule:同一个线程中的每个Action都happens-before于出现在其后的任何一个Action。
•Monitor lock rule:对一个监视器的解锁happens-before于每一个后续对同一个监视器的加锁。
•Volatile variable rule:对volatile字段的写入操作happens-before于每一个后续的同一个字段的读操作。
•Thread start rule:Thread.start()的调用会happens-before于启动线程里面的动作。
•Thread termination rule:Thread中的所有动作都happens-before于其他线程检查到此线程结束或者Thread.join()中返回或者Thread.isAlive()==false。
•Interruption rule:一个线程A调用另一个另一个线程B的interrupt()都happens-before于线程A发现B被A中断(B抛出异常或者A检测到B的isInterrupted()或者interrupted())。
•Finalizer rule:一个对象构造函数的结束happens-before与该对象的finalizer的开始
•Transitivity:如果A动作happens-before于B动作,而B动作happens-before与C动作,那么A动作happens-before于C动作。
因为CPU是可以不按我们写代码的顺序执行内存的存取过程的,也就是指令会乱序或并行运行, 只有上面的happens-before所规定的情况下,才保证顺序性。





上面的 关于 volatile 的谁给我分析一下?? 谢谢了
meran 2011-08-19
  • 打赏
  • 举报
回复


V get(Object key, int hash) {
if (count != 0) { // read-volatile
HashEntry<K,V> e = getFirst(hash);
while (e != null) {
if (e.hash == hash && key.equals(e.key)) {
V v = e.value;
if (v != null)
return v;
return readValueUnderLock(e); // recheck
}
e = e.next;
}
}
return null;
}



V put(K key, int hash, V value, boolean onlyIfAbsent) {
lock();
try {
int c = count;
if (c++ > threshold) // ensure capacity
rehash();
HashEntry<K,V>[] tab = table;
int index = hash & (tab.length - 1);
HashEntry<K,V> first = tab[index];
HashEntry<K,V> e = first;
while (e != null && (e.hash != hash || !key.equals(e.key)))
e = e.next;

V oldValue;
1. if (e != null) { //在这一句的时候A线程挂起 B线程执行,会找到元素并直接返回
2. oldValue = e.value; //旧值被更新
if (!onlyIfAbsent)
e.value = value;
}
else {
oldValue = null;
++modCount;
tab[index] = new HashEntry<K,V>(key, hash, first, value);
count = c; // write-volatile
}
return oldValue;
} finally {
unlock();
}
}







看我的注释
用上面的例子 容器里原来有 <"a","bc"> 线程A调用 put<"a","de">; 在执行到1处的时候,线程A发现容器中原来有 key a 所以准备更新 value , 此时 A挂起 B开始执行 并调用 get(a) 方法 ,B 发现拿到了 value "bc" ,就返回了。然后A 获得CPU 开始执行,进行更新 操作 <"a","de">
如果根据 语意 , A的 put 操作在前,B的get 在后 自然应该保证 读数据的正确性 ,这样不算脏读么?
meran 2011-08-19
  • 打赏
  • 举报
回复
[Quote=引用 6 楼 ticmy 的回复:]
我现在调用 put插入 <"a","cd"> , 在 "bc" 被赋值为 "cd" 之前, get("a") 方法被调用 ,get拿到了"bc"并返回,此时 put
才把Value "bc" 赋值为 "cd" 。


-----------

你拿的时候还没赋值,难道不应该把"bc"给你?

就像你要投资,手头上只有5w块,在你投资完成1s钟后发现中了50w,难道你在考虑投资额的时……
[/Quote]

你根本没看上面代码啊。。你仔细看看就会发现问题了,至于你说的例子,我已经说了是两个线程了。 至于你说数据库 ,数据库没有锁机制么? 你一个连接更新当然是要加写锁吧?, 那你取的时候也要加读锁吧?,

这个例子明显和上面不一样, 因为 get 并没有加锁。
龙四 2011-08-19
  • 打赏
  • 举报
回复
就算是数据库,也要在同一个connection(相当于这里的线程)中先update后select才能取到update的值
龙四 2011-08-19
  • 打赏
  • 举报
回复
换言之,数据库,你先select,然后别人update了,你还想拿到别人update的值不成?
龙四 2011-08-19
  • 打赏
  • 举报
回复
我现在调用 put插入 <"a","cd"> , 在 "bc" 被赋值为 "cd" 之前, get("a") 方法被调用 ,get拿到了"bc"并返回,此时 put
才把Value "bc" 赋值为 "cd" 。


-----------

你拿的时候还没赋值,难道不应该把"bc"给你?

就像你要投资,手头上只有5w块,在你投资完成1s钟后发现中了50w,难道你在考虑投资额的时候要把这50w也算进去?
BadPattern 2011-08-19
  • 打赏
  • 举报
回复
那我只能推荐你看此文章了:
http://www.ibm.com/developerworks/cn/java/j-jtp08223/index.html
meran 2011-08-19
  • 打赏
  • 举报
回复
[Quote=引用 2 楼 shuwei003 的回复:]
貌似是

Java code

import java.util.concurrent.ConcurrentHashMap;


public class Test2 extends Thread{
static ConcurrentHashMap<String, String> chm = new ConcurrentHashMap<String, String>();
……
[/Quote]

其实我说的并不是这个意思。。。感觉比较难描述。。。
meran 2011-08-19
  • 打赏
  • 举报
回复
[Quote=引用 1 楼 dr8737010 的回复:]
与 Hashtable 和 HashMap 不同的是,ConcurrentHashMap 没有使用单一的集合锁(collection lock),而是使用了一个固定的锁池,这个锁池形成了bucket 集合的一个分区。
ConcurrentHashMap可以保证并发的put总是会安全执行,不会破坏数据结构,但是正如SynchronizedMap一样,集合的复合操作有逻辑上同步的必要呀。
[/Quote]
我主要关心的是。 get 是否会独到脏数据 , 与put 是否安全没关系
shuwei003 2011-08-19
  • 打赏
  • 举报
回复
貌似是

import java.util.concurrent.ConcurrentHashMap;


public class Test2 extends Thread{
static ConcurrentHashMap<String, String> chm = new ConcurrentHashMap<String, String>();
public static void main(String[] args) throws InterruptedException {
chm.put("one", "one");
new Test2().start();
Thread.sleep(200);
chm.put("one", "TTTT");
System.out.println(chm.get("one"));
Thread.sleep(1000);
System.out.println(chm.get("one"));
}
public void run(){
try {
System.out.println("thread2 changing!");
Thread.sleep(1000);
chm.put("one", "two");
System.out.println("thread2 change over");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}

输出:
thread2 changing!
TTTT
thread2 change over
two
BadPattern 2011-08-19
  • 打赏
  • 举报
回复
与 Hashtable 和 HashMap 不同的是,ConcurrentHashMap 没有使用单一的集合锁(collection lock),而是使用了一个固定的锁池,这个锁池形成了bucket 集合的一个分区。
ConcurrentHashMap可以保证并发的put总是会安全执行,不会破坏数据结构,但是正如SynchronizedMap一样,集合的复合操作有逻辑上同步的必要呀。

62,614

社区成员

发帖
与我相关
我的任务
社区描述
Java 2 Standard Edition
社区管理员
  • Java SE
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧