zoukankan      html  css  js  c++  java
  • 并发容器——ConcurrentHashMap

    ConcurreentHashMap的实现原理与使用

    ConcurrentHashMap是线程安全且高效的HashMap。

    为什么要使用ConcurrentHashMap

    在并发编程中使用HashMap可能导致程序死循环。而使用线程安全的HashTable效率又非常低下,基于以上两个原因,便有了ConcurrentHashMap的登场机会。

    1. 线程不安全的HashMap

      在多线程环境下,使用HashMap进行put操作会引起死循环,导致CPU利用率接近100%,所以在并发情况下不能使用HashMap。

      HashMap在并发执行put操作时会引起死循环,是因为多线程会导致HashMap的Entry链表形成环形数据结构,一旦形成环形数据结构,Entry的next节点永远不为空,,就会产生死循环获取Entry。

    2. 效率低下的HashTable

      HashTable容器使用了synchronized来保证线程安全,但在线程竞争激烈的情况下HashTable的效率非常低下。因为当一个线程访问HashTable的同步方法,其他线程也访问HashTable的同步方法时,会进入阻塞或轮询状态。

    3. ConcurrentHashMap的锁分段技术可有效提升并发访问率

      容器里有很多把锁,每一把锁用于锁容器中其中一部分数据,那么当多线程访问容器里不同数据段的数据时,线程间就不会存在锁竞争,从而可以有效提高并发访问效率,这就是ConcurrentHashMap所使用的锁分段技术。

    ConcurrentHashMap的结构

    ConcurrentHashMap是由Segment数组结构和HashEntry数据结构组成。Segment是一种可重入锁(ReentrantLock),在ConcurrentHashMap里扮演锁的角色;HashEntry则用于存储键值对数据。

    ConcurrentHashMap的操作

    1. get操作

      Segment的get操作实现非常简单和高效。先经过一次再散列,然后使用这个散列值通过散列运算定位到Segment,再通过散列算法定位到元素:

       public V get(Object key) {
              Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
              int h = spread(key.hashCode());
              if ((tab = table) != null && (n = tab.length) > 0 &&
                  (e = tabAt(tab, (n - 1) & h)) != null) {
                  if ((eh = e.hash) == h) {
                      if ((ek = e.key) == key || (ek != null && key.equals(ek)))
                          return e.val;
                  }
                  else if (eh < 0)
                      return (p = e.find(h, key)) != null ? p.val : null;
                  while ((e = e.next) != null) {
                      if (e.hash == h &&
                          ((ek = e.key) == key || (ek != null && key.equals(ek))))
                          return e.val;
                  }
              }
              return null;
          }
      
    2. put操作

      由于put操作方法里需要对共享变量进行写操作,所以为了线程安全,在操作共享变量时必须加锁。

      public V put(K key, V value) {
              return putVal(key, value, false);
          }
      final V putVal(K key, V value, boolean onlyIfAbsent) {
              if (key == null || value == null) throw new NullPointerException();
              int hash = spread(key.hashCode());
              int binCount = 0;
              for (Node<K,V>[] tab = table;;) {
                  Node<K,V> f; int n, i, fh;
                  if (tab == null || (n = tab.length) == 0)
                      tab = initTable();
                  else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
                      if (casTabAt(tab, i, null,
                                   new Node<K,V>(hash, key, value, null)))
                          break;                   // no lock when adding to empty bin
                  }
                  else if ((fh = f.hash) == MOVED)
                      tab = helpTransfer(tab, f);
                  else {
                      V oldVal = null;
                      synchronized (f) {
                          if (tabAt(tab, i) == f) {
                              if (fh >= 0) {
                                  binCount = 1;
                                  for (Node<K,V> e = f;; ++binCount) {
                                      K ek;
                                      if (e.hash == hash &&
                                          ((ek = e.key) == key ||
                                           (ek != null && key.equals(ek)))) {
                                          oldVal = e.val;
                                          if (!onlyIfAbsent)
                                              e.val = value;
                                          break;
                                      }
                                      Node<K,V> pred = e;
                                      if ((e = e.next) == null) {
                                          pred.next = new Node<K,V>(hash, key,
                                                                    value, null);
                                          break;
                                      }
                                  }
                              }
                              else if (f instanceof TreeBin) {
                                  Node<K,V> p;
                                  binCount = 2;
                                  if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
                                                                 value)) != null) {
                                      oldVal = p.val;
                                      if (!onlyIfAbsent)
                                          p.val = value;
                                  }
                              }
                          }
                      }
                      if (binCount != 0) {
                          if (binCount >= TREEIFY_THRESHOLD)
                              treeifyBin(tab, i);
                          if (oldVal != null)
                              return oldVal;
                          break;
                      }
                  }
              }
              addCount(1L, binCount);
              return null;
          }
      

      (1)是否需要扩容

      在插入元素前先会先判断Segment里的HashEntry数组是否超过容量(threshold),如果超过阈值,则对数组进行扩容

      (2)如何扩容

      在扩容的时候,首先会创建一个容量是原来容量两倍的数组,然后对原数组里的元素进行再散列后插入到新的数组里。为了高效,ConcurrentHashMap不会对整个容器进行扩容,而只对某个segment进行扩容。

    3. size操作

      如果要统计整个ConcurrentHashMap里元素的大小,就必须统计所有Segment里的元素的大小后求和。

      public int size() {
              long n = sumCount();
              return ((n < 0L) ? 0 :
                      (n > (long)Integer.MAX_VALUE) ? Integer.MAX_VALUE :
                      (int)n);
          }
      
  • 相关阅读:
    分布式数据库拆分表常用的方法
    linux服务器502错误详解【转载】
    全国各城市代码
    Linux下git安装
    linux上访问windows目录
    百度技术总监谈12306高性能海量并发网站架构设计
    Ubuntu 10.04 安装无线网卡驱动。
    晕菜, silverlight !
    linux 软件记录.
    硬盘安装 Ubuntu10.04
  • 原文地址:https://www.cnblogs.com/Tu9oh0st/p/10147163.html
Copyright © 2011-2022 走看看