1. 程式人生 > >JDK9 ConcurrentHashMap實現原理(一)

JDK9 ConcurrentHashMap實現原理(一)

文章目錄

JDK9 ConcurrentHashMap實現原理(一)

資料結構

JDK1.7中採用Segment + HashEntry的方式進行實現.使用ReentrantLock實現加鎖操作。
JDK1.8中放棄了Segment臃腫的設計,取而代之的是採用Node + CAS + Synchronized來保證併發安全進行實現.結構類似於HashMap,陣列+連結串列+紅黑樹。
在這裡插入圖片描述

私有屬性

靜態屬性

  • private static final int MAXIMUM_CAPACITY = 1 << 30;
    最大的容量,必須為2的平方。

  • private static final int DEFAULT_CAPACITY = 16;
    預設的初始容量,也是2的平方

  • static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
    最大的陣列大小。

  • private static final int DEFAULT_CONCURRENCY_LEVEL = 16;
    預設的併發等級,只在writeObject中用了。

  • private static final float LOAD_FACTOR = 0.75f;
    載入因子,只在writeObject中用了。不像HashMap中的用法。

  • static final int TREEIFY_THRESHOLD = 8;
    當某個陣列位置上的節點數量超過8時,則將單鏈表結構轉換為紅黑樹結構。

  • static final int UNTREEIFY_THRESHOLD = 6;
    當某個陣列位置上的節點數量小於6時,則將紅黑樹結構轉換為單鏈表結構。

  • static final int MIN_TREEIFY_CAPACITY = 64;
    在轉換成紅黑樹之前,還需要檢測當前table的大小是否大於等於MIN_TREEIFY_CAPACITY,小於不會轉換成紅黑樹,而是重新擴充套件table的大小。

  • private static final int MIN_TRANSFER_STRIDE = 16;

  • private static final int RESIZE_STAMP_BITS = 16;

  • private static final int MAX_RESIZERS = (1 << (32 - RESIZE_STAMP_BITS)) - 1;
    擴容時可利用的最大執行緒

  • private static final int RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS;

  • static final int MOVED = -1; // hash for forwarding nodes
    static final int TREEBIN = -2; // hash for roots of trees
    static final int RESERVED = -3; // hash for transient reservations
    static final int HASH_BITS = 0x7fffffff; // usable bits of normal node hash

  • static final int NCPU = Runtime.getRuntime().availableProcessors();
    當前機器的CPU的核心處理器數量,transfer擴容時會用到。

相關節點

  • Node:該類用於構造table[],只讀節點(不提供修改方法)。是一個單鏈表結構。
  static class Node<K,V> implements Map.Entry<K,V> {
        //節點的雜湊值
        final int hash;
        //建
        final K key;
        //值
        volatile V val;
        //指向下一個節點,說明是單鏈表結構
        volatile Node<K,V> next;

        Node(int hash, K key, V val) {
            this.hash = hash;
            this.key = key;
            this.val = val;
        }

        Node(int hash, K key, V val, Node<K,V> next) {
            this(hash, key, val);
            this.next = next;
        }

        public final K getKey()     { return key; }
        public final V getValue()   { return val; }
        public final int hashCode() { return key.hashCode() ^ val.hashCode(); }
        public final String toString() {
            return Helpers.mapEntryToString(key, val);
        }
        public final V setValue(V value) {
            throw new UnsupportedOperationException();
        }

        public final boolean equals(Object o) {
            Object k, v, u; Map.Entry<?,?> e;
            return ((o instanceof Map.Entry) &&
                    (k = (e = (Map.Entry<?,?>)o).getKey()) != null &&
                    (v = e.getValue()) != null &&
                    (k == key || k.equals(key)) &&
                    (v == (u = val) || v.equals(u)));
        }

        /**
         * Virtualized support for map.get(); overridden in subclasses.
         */
        Node<K,V> find(int h, Object k) {
            Node<K,V> e = this;
            if (k != null) {
                do {
                    K ek;
                    if (e.hash == h &&
                        ((ek = e.key) == k || (ek != null && k.equals(ek))))
                        return e;
                } while ((e = e.next) != null);
            }
            return null;
        }
    }
  • TreeBin:紅黑樹結構。
  • TreeNode:紅黑樹節點。
static final class TreeNode<K,V> extends Node<K,V> {
       //父節點
        TreeNode<K,V> parent;  // red-black tree links
        //左節點
        TreeNode<K,V> left;
        //右節點
        TreeNode<K,V> right;
        //
        TreeNode<K,V> prev;    // needed to unlink next upon deletion
        //節點的顏色
        boolean red;

        TreeNode(int hash, K key, V val, Node<K,V> next,
                 TreeNode<K,V> parent) {
            super(hash, key, val, next);
            this.parent = parent;
        }

        Node<K,V> find(int h, Object k) {
            return findTreeNode(h, k, null);
        }
        //使用this這個樹形節點作為根節點,尋找目標節點。
        final TreeNode<K,V> findTreeNode(int h, Object k, Class<?> kc) {}
    
    }
  • ForwardingNode:臨時節點(擴容時使用)。

構造器

  • 無參構造器
 public ConcurrentHashMap() {
    }

指定初始容量

 public ConcurrentHashMap(int initialCapacity) {
    if (initialCapacity < 0)
        throw new IllegalArgumentException();
    int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ?
               MAXIMUM_CAPACITY :
               tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));
    this.sizeCtl = cap;
}

這裡使用tableSizeFor將輸入的容量轉換為2的平方。

private static final int tableSizeFor(int c) {
        int n = c - 1;
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }

輸入容量:3 , 輸出:4;
輸入容量:11 , 輸出:16;
輸入容量:17 , 輸出:32;
不確定這裡為什麼要這麼處理(initialCapacity + (initialCapacity >>> 1) + 1));雖然直接initialCapacity的結果也是一樣的。

  • 還可以指定載入因子和concurrencyLevel。
    可以看到這兩個引數在新版本中只是在初始化才會用到,其他地方不會用到,注意這裡和HashMap的區別。
 public ConcurrentHashMap(int initialCapacity,
                       float loadFactor, int concurrencyLevel) {
    if (!(loadFactor > 0.0f) || initialCapacity < 0 || concurrencyLevel <= 0)
        throw new IllegalArgumentException();
    if (initialCapacity < concurrencyLevel)   // Use at least as many bins
        initialCapacity = concurrencyLevel;   // as estimated threads
    long size = (long)(1.0 + (long)initialCapacity / loadFactor);
    int cap = (size >= (long)MAXIMUM_CAPACITY) ?
        MAXIMUM_CAPACITY : tableSizeFor((int)size);
    this.sizeCtl = cap;
}
  • 和大多數集合一樣,都可以由其他集合元素作為初始元素。
 public ConcurrentHashMap(Map<? extends K, ? extends V> m) {
        this.sizeCtl = DEFAULT_CAPACITY;
        putAll(m);
    }

Hash值計算

可以看出 計算出的hash都是正值

static final int HASH_BITS = 0x7fffffff; // usable bits of normal node hash
static final int spread(int h) {
  return (h ^ (h >>> 16)) & HASH_BITS;
}

新增元素

1.key值和value都不能null
2.onlyIfAbsent=true : 如果當前key值已經存在,則不 更新為新值, false: 不管什麼情況都會更新為新值。

 final V putVal(K key, V value, boolean onlyIfAbsent) {
  if (key == null || value == null) throw new NullPointerException();
  //獲取hash值
  int hash = spread(key.hashCode());
  int binCount = 0;
  for (Node<K,V>[] tab = table;;) {
      Node<K,V> f; int n, i, fh; K fk; V fv;
      //如果table為空,說明之前沒有放入元素
      if (tab == null || (n = tab.length) == 0)
          tab = initTable();
      //如果要插入的位置沒有節點資料     
      else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
          //如果陣列的桶是空的,則嘗試插入資料,直到成功才中斷當前迴圈,使用CAS演算法
          if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value)))
              break;                   // no lock when adding to empty bin
      }
      //到此插入資料成功
      //如果在插入的時候,節點是一個forwordingNode狀態,表示正在擴容,那麼當前執行緒進行幫助擴容            
      else if ((fh = f.hash) == MOVED)
          tab = helpTransfer(tab, f);
      else if (
      		  //如果onlyIfAbsent為true,也就是隻要key已經存在,就不寫入新值
              onlyIfAbsent 
              //擴容已經結束,fh 和傳入key的值一樣
              &&   fh == hash   // check first node
              //
              && ((fk = f.key) == key || fk != null && key.equals(fk)) 
              && (fv = f.val) != null)
          return fv;
      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);
                              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;
                      }
                  }
                  else if (f instanceof ReservationNode)
                      throw new IllegalStateException("Recursive update");
              }
          }
          if (binCount != 0) {
              if (binCount >= TREEIFY_THRESHOLD)
                  treeifyBin(tab, i);
              if (oldVal != null)
                  return oldVal;
              break;
          }
      }
  }
  addCount(1L, binCount);
  return null;
}

初始化陣列

private final Node<K,V>[] initTable() {
  Node<K,V>[] tab; int sc;
   while ((tab = table) == null || tab.length == 0) {
       if ((sc = sizeCtl) < 0)
           Thread.yield(); // lost initialization race; just spin
       else if (U.compareAndSetInt(this, SIZECTL, sc, -1)) {
           try {
               if ((tab = table) == null || tab.length == 0) {
                   int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
                   @SuppressWarnings("unchecked")
                   Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
                   table = tab = nt;
                   sc = n - (n >>> 2);
               }
           } finally {
               sizeCtl = sc;
           }
           break;
       }
   }
   return tab;
}

未完待續。。。。。。。。。。