ConcurrentHashMap实现原理

来源:互联网 发布:还原网络设置会怎样 编辑:程序博客网 时间:2024/06/05 19:03

ConcurrentHashMap是一个线程安全的Hashtable,它的主要功能是提供了一组Hashtable功能相同但是线程安全的方法。ConcurrentHashMap可以做到读取数据不加锁,并且其内部的结构可以让其在进行写操作的时候可以将锁的粒度保持的很小,不会对整个ConcurrentHashMap加锁。

ConcurrentHashMap的内部结构:

ConcurrentHashMap为了提高本身的并发能力,在内部采用了一个叫做Segment的结构,一个Segment其实就是一个类似Hashtablede 结构,Segment内部维护了一个数组和链表


从上面的结构我们可以了解到,ConcurrentHashMap定位到一个元素的过程需要进行两次Hash操作,第一次Hash定位到Segment,第二次Hash定位到元素所在的链表的头部,因此,这种结构带来的副作用就是Hash的过程要比普通的HashMap要长,但是带来的好处就是写操作的时候可以只对元素所在的Segment进行加锁即可,不会影响到其他的Segment。

Segment的数据结构:

static final class Segment<K,V> extends ReentrantLock implements Serializable {      transient volatile int count;      transient int modCount;      transient int threshold;      transient volatile HashEntry<K,V>[] table;      final float loadFactor;  }  

  • count:Segment中元素的数量
  • modCount:对table的大小造成影响的操作的数量(比如put或者remove操作)
  • threshold:阈值,Segment里面元素的数量超过这个值依旧就会对Segment进行扩容
  • table:链表数组,数组中的每一个元素代表了一个链表的头部
  • loadFactor:负载因子,用于确定threshold
HashEntry的结构
Segment中的元素是以HashEntry的形式存放在链表数组中的

static final class HashEntry<K,V> {      final K key;      final int hash;      volatile V value;      final HashEntry<K,V> next;  }  
ConcurrentHashMap的get操作

ConcurrentHashMap的get操作是不用加锁的。

public V get(Object key) {      int hash = hash(key.hashCode());      return segmentFor(hash).get(key, hash);  }  
第三行中,segmentFor这个函数用于确定操作应该在哪一个segment中进行,几乎对ConcurrentHashMap的所有操作都需要用到这个函数。

final Segment<K,V> segmentFor(int hash) {      return segments[(hash >>> segmentShift) & segmentMask];  }  
在确定了需要在哪一个segment中进行操作以后,接下来的事情就是调用对应的Segment的get方法:

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;  } 
在第三行调用了getFirst来取得链表的头部,在确定了链表的头部以后,就可以对整个链表进行遍历。

HashEntry<K,V> getFirst(int hash) {      HashEntry<K,V>[] tab = table;      return tab[hash & (tab.length - 1)];  }  
ConcurrentHashMap的put操作:

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();      }  }  
首先对Segment的put操作是加锁完成的,第8和第9行的操作就是getFirst的过程,确定链表头部的位置。

第11行这里的while循环就是在链表中寻找和要put的元素相同key的元素,如果找到,就直接更新key的value,如果没有找到,则进入21行,生成一个新的HashEntry并且把它加入到整个Segment的头部,然后再更新count的值。







1 0
原创粉丝点击