HashMap面试之ConcurrentHashMap

489 阅读4分钟

以下ConcurrentHashMap源码的解析都是基于java8来讲解的。

ConcurrentHashMap的线程安全性是通过synchronized+CAS实现的。ConcurrentHashMap的数据结构和HashMaori大致相同,下面主要讲一下不同的地方

常量

// 这个属性用于控制table的初始化和扩容
// sizeCtl<0表示table正在初始化或者扩容
// sizeCtl=-1 表示table正在初始化
// sizeCtl<-1 表示table正在扩容
// sizeCtl默认为0
private transient volatile int sizeCtl; 

// 默认并发度,同时允许多少个线程访问
private static final int DEFAULT_CONCURRENCY_LEVEL = 16;

// 表示node正在被移动
static final int MOVED     = -1;

// 表示node正在进行树化
static final int TREEBIN   = -2;

// 表示node运行computeIfAbsent等方法
static final int RESERVED  = -3;

方法

  1. initTable方法

    table的初始化是懒惰的,是在put操作的时候进行的,如果put时发现table没有初始化,才进行table的初始化操作。

    private final Node<K,V>[] initTable() {
      Node<K,V>[] tab; int sc;
      while ((tab = table) == null || tab.length == 0) {
        // sizeCtl<0说明有线程正在初始化table,所以当前线程让出CPU
        if ((sc = sizeCtl) < 0)
          Thread.yield(); // lost initialization race; just spin
        //sizeCtl不小于0的话,通过cas把sizeCtl改为-1
        else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
          try {
            //这里进行table的二次校验,是因为多线程下,有可能两个线程同时都判断table为null,
            //一个线程cas设置sizeCtl=-1成功进行初始化,另一个竞争sizeCtl,
            //等第一个线程初始化完成后,第二个又进行了初始化
            if ((tab = table) == null || tab.length == 0) {
              //初始化时,sc=-1,所以n=DEFAULT_CAPACITY(16)
              int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
              @SuppressWarnings("unchecked")
              Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
              table = tab = nt;
              //因为sizeCtl还用于扩容,所以这里设置sc的值为阈值
              //因为负载因子是0.75,也就是1-0.25,所以用n>>>2表示0.25这部分
              sc = n - (n >>> 2);
            }
          } finally {
            sizeCtl = sc;
          }
          break;
        }
      }
      return tab;
    }
    
  2. get方法

    首先通过spread方法计算出hash值,然后计算出所在table的index值

    public V get(Object key) {
      Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
      int h = spread(key.hashCode());
      //判断所属index的值不为空
      if ((tab = table) != null && (n = tab.length) > 0 &&
          (e = tabAt(tab, (n - 1) & h)) != null) {
        //首先判断node的头是不是要找的值
        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循环node链,找到hash相同且key equal的值
        while ((e = e.next) != null) {
          if (e.hash == h &&
              ((ek = e.key) == key || (ek != null && key.equals(ek))))
            return e.val;
        }
      }
      return null;
    }
    
  3. put方法

    put方法通过synchronized+CAS的方式实现线程安全,它锁住的是table的某个index下的值,所以不会影响到其他的值,所以锁的粒度很小

    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;
        //table为空的话,初始化table
        if (tab == null || (n = tab.length) == 0)
          tab = initTable();
        //如果没有hash冲突的话,直接通过cas设置值
        //如果cas失败,则可能是其他线程在插入,则继续下一步
        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
        }
        //如果table[i]的hash值为-1,则表示有其他线程在扩容,则帮助其扩容
        else if ((fh = f.hash) == MOVED)
          tab = helpTransfer(tab, f);
        else {
          V oldVal = null;
          //hash冲突之后,使用synchronized锁住所在index的node
          synchronized (f) {
            if (tabAt(tab, i) == f) {
              //fh>=0说明是链表结构
              if (fh >= 0) {
                binCount = 1;
                //循环链表,put值
                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;
                  //node链走到结尾也没有冲突的话,则构造新node,加到尾部
                  if ((e = e.next) == null) {
                    pred.next = new Node<K,V>(hash, key,
                                              value, null);
                    break;
                  }
                }
              }
              //fh<0说明是红黑树结构,采用红黑树的方式put
              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) {
            //检查node链的长度,超过8则进行树化
            if (binCount >= TREEIFY_THRESHOLD)
              treeifyBin(tab, i);
            if (oldVal != null)
              return oldVal;
            break;
          }
        }
      }
      addCount(1L, binCount);
      return null;
    }