zoukankan      html  css  js  c++  java
  • ConcurrentHashMap之实现细节 4

    http://www.javaeye.com/topic/344876

    该方法也是在持有段锁的情况下执行的,首先判断是否需要rehash,需要就先rehash。接着是找是否存在同样一个key的结点,如果存在就直 接替换这个结点的值。否则创建一个新的结点并添加到hash链的头部,这时一定要修改modCount和count的值,同样修改count的值一定要放 在最后一步。put方法调用了rehash方法,reash方法实现得也很精巧,主要利用了table的大小为2^n,这里就不介绍了。

    修改操作还有putAll和replace。putAll就是多次调用put方法,没什么好说的。replace甚至不用做结构上的更改,实现要比put和delete要简单得多,理解了put和delete,理解replace就不在话下了,这里也不介绍了。

    获取操作

    首先看下get操作,同样ConcurrentHashMap的get操作是直接委托给Segment的get方法,直接看Segment的get方法:

    Java代码 <embed height="15" width="14" pluginspage="http://www.macromedia.com/go/getflashplayer" type="application/x-shockwave-flash" allowscriptaccess="always" quality="high" flashvars="clipboard=%20%20%20%20%20%20%20%20V%20get(Object%20key%2C%20int%20hash)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20(count%20!%3D%200)%20%7B%20%2F%2F%20read-volatile%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20HashEntry%3CK%2CV%3E%20e%20%3D%20getFirst(hash)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20while%20(e%20!%3D%20null)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20(e.hash%20%3D%3D%20hash%20%26%26%20key.equals(e.key))%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20V%20v%20%3D%20e.value%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20(v%20!%3D%20null)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20v%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20readValueUnderLock(e)%3B%20%2F%2F%20recheck%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20e%20%3D%20e.next%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20null%3B%0A%20%20%20%20%20%20%20%20%7D" src="http://www.javaeye.com/javascripts/syntaxhighlighter/clipboard_new.swf" lk_mediaid="lk_juiceapp_mediaPopup_1236652704310" lk_media="yes">
    1. V get(Object key, int hash) {  
    2.     if (count != 0) { // read-volatile  
    3.          HashEntry<K,V> e = getFirst(hash);  
    4.         while (e != null) {  
    5.             if (e.hash == hash && key.equals(e.key)) {  
    6.                  V v = e.value;  
    7.                 if (v != null)  
    8.                     return v;  
    9.                 return readValueUnderLock(e); // recheck  
    10.              }  
    11.              e = e.next;  
    12.          }  
    13.      }  
    14.     return null;  
    15. }  
    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; }

    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的时候就进行了判断,如果为空就要抛NullPointerException。空值的唯一源头就是HashEntry中的默认值,因为 HashEntry中的value不是final的,非同步读取有可能读取到空值。仔细看下put操作的语句:tab[index] = new HashEntry<K,V>(key, hash, first, value),在这条语句中,HashEntry构造函数中对value的赋值以及对tab[index]的赋值可能被重新排序,这就可能导致结点的值为 空。这种情况应当很罕见,一旦发生这种情况,ConcurrentHashMap采取的方式是在持有锁的情况下再读一遍,这能够保证读到最新的值,并且一 定不会为空值。

    Java代码
    1. V readValueUnderLock(HashEntry<K,V> e) {  
    2.      lock();  
    3.     try {  
    4.         return e.value;  
    5.      } finally {  
    6.          unlock();  
    7.      }  
    8. }  
    V readValueUnderLock(HashEntry<K,V> e) { lock(); try { return e.value; } finally { unlock(); } }

    另一个操作是containsKey,这个实现就要简单得多了,因为它不需要读取值:

    Java代码 <embed height="15" width="14" pluginspage="http://www.macromedia.com/go/getflashplayer" type="application/x-shockwave-flash" allowscriptaccess="always" quality="high" flashvars="clipboard=%20%20%20%20%20%20%20%20boolean%20containsKey(Object%20key%2C%20int%20hash)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20(count%20!%3D%200)%20%7B%20%2F%2F%20read-volatile%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20HashEntry%3CK%2CV%3E%20e%20%3D%20getFirst(hash)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20while%20(e%20!%3D%20null)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20(e.hash%20%3D%3D%20hash%20%26%26%20key.equals(e.key))%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20true%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20e%20%3D%20e.next%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20false%3B%0A%20%20%20%20%20%20%20%20%7D" src="http://www.javaeye.com/javascripts/syntaxhighlighter/clipboard_new.swf" lk_mediaid="lk_juiceapp_mediaPopup_1236652704322" lk_media="yes">
    1. boolean containsKey(Object key, int hash) {  
    2.     if (count != 0) { // read-volatile  
    3.          HashEntry<K,V> e = getFirst(hash);  
    4.         while (e != null) {  
    5.             if (e.hash == hash && key.equals(e.key))  
    6.                 return true;  
    7.              e = e.next;  
    8.          }  
    9.      }  
    10.     return false;  
    11. }  
    boolean containsKey(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)) return true; e = e.next; } } return false; }

    跨段操作

  • 相关阅读:
    反编译Silverlight项目
    Android 程序中像素(px)跟 单位dp(dip)之间的转换
    保存RichTextBox的文本到数据库,以及如何对RichTextBox的Document做绑定
    做事情要有五个w一个h,做项目也受用
    把RichTextBox的内容保存到数据库
    Android横竖屏切换总结
    64操作系统编译出错。The 'Microsoft.Jet.OLEDB.4.0' provider is not registered on the local machine.
    超过连接数时强行登陆3389(远程桌面)的方法
    Android 4.0新增WiFiDirect功能
    前缀和 与 树状数组
  • 原文地址:https://www.cnblogs.com/danghuijian/p/4400692.html
Copyright © 2011-2022 走看看