【P说】JDK 1.8 ConcurrentHashMap分析

简介

相对JDK 1.7,ConcurrentHashMap在JDK 1.8有了很大的优化改动,底层的实现由原来的“segement数组+table数组+链表”改为了“node数组+链表或者红黑树”。
关于ConcurrentHashMap 在JDK1.7的分析,可以查看:【P说】JDK 1.7及以前ConcurrentHashMap分析

数据结构

在这里插入图片描述
可以看到,在JDK 1.8的时候ConcurrentHashMap在底层实现上取消了原本的segement数组,改为了“node数组+链表或者红黑树”的实现。

  1. 原本每一个segment是个一个ReentrantLock,现在通过CAS + synchronized来保证并发更新的安全。其中,synchronized锁的是Node数组元素,原本在1.7的时候segment是固定不变,而在JDK 1.8 Node数组是会扩张的,也就是锁的粒度会变小,减少并发冲突的概率。(synchronized的性能在1.8的时候已经有了很大的提升)
  2. 数组+链表+红黑树的实现,JDK 1.7的时候主要是通过链表来存放数据,在JDK 1.8的时候ConcurrentHashMap改用数组+链表+红黑树的方式,在链表长度超过8的时候会转为红黑树存储,而在红黑树中元素少于6时,又会变回链表。

成员变量


// 默认容量16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; 
 
// 最大容量
static final int MAXIMUM_CAPACITY = 1 << 30;    
 
// 默认负载因子0.75
static final float DEFAULT_LOAD_FACTOR = 0.75f; 
 
// 链表节点转换红黑树节点的阈值, 8个节点转
static final int TREEIFY_THRESHOLD = 8; 
 
// 红黑树节点转换链表节点的阈值, 6个节点转
static final int UNTREEIFY_THRESHOLD = 6;   
 
// 转红黑树时, table的最小长度
static final int MIN_TREEIFY_CAPACITY = 64;

//链表节点
static class Node<K,V> implements Map.Entry<K,V> {
    final int hash;
    final K key;
    volatile V val;
    volatile Node<K,V> next;
	//....
}
//红黑树节点
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;
}
//树根节点
static final class TreeBin<K,V> extends Node<K,V> {
        TreeNode<K,V> root;
        volatile TreeNode<K,V> first;
        volatile Thread waiter;
        volatile int lockState;
        // values for lockState
        static final int WRITER = 1; // set while holding write lock
        static final int WAITER = 2; // set when waiting for write lock
        static final int READER = 4; // increment value for setting read lock
}

Hash算法

通过两次hash获得hash值

int h = spread(key.hashCode()); //第一次hashCode

static final int spread(int h) {
     return (h ^ (h >>> 16)) & HASH_BITS; //第一次获得的哈希值,异或自己的高16位。后面与上HASH_BITS是为了保证获得的是整数,HASH_BITS为二进制31个1。
 }

源码分析

构造函数

什么都没做,只是初始化一些成员变量。

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;
}

public ConcurrentHashMap(Map<? extends K, ? extends V> m) {
    this.sizeCtl = DEFAULT_CAPACITY;
    putAll(m);
}

public ConcurrentHashMap(int initialCapacity, float loadFactor) {
    this(initialCapacity, loadFactor, 1);
}

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;
}

Get

get方法处理比较简单,先根据hash值来确定node数组的下标,然后先判断存在node数组中的元素是不是就是我们想要的数据,如果不是,根据红黑树或者是链表,分成两种情况去查找。

public V get(Object key) {
     Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
     int h = spread(key.hashCode()); //获得hash值
     if ((tab = table) != null && (n = tab.length) > 0 &&
         (e = tabAt(tab, (n - 1) & h)) != null) { //判断在node数组的哪个下标下
         if ((eh = e.hash) == h) {  //判断是否当前node数组 保存的数据就是想要的数据
             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;
 }

Put操作

初始化node数组:
这里要先理解变量sizeCtl的作用:

  1. 负数:表示进行初始化或者扩容,-1表示正在初始化,-N,表示有N-1个线程正在进行扩容
  2. 正数:0 表示还没有被初始化,>0的数,初始化或者是下一次进行扩容的阈值
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(); //让出CPU执行权
        else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) { //改变sizeCtl的值为-1,表示当前线程进行初始化,因为没有拿锁,所以使用CAS操作,保证只有一个线程可以设置成功,并且返回true
            try {
            	//初始化node数组
                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); //sc等4/3n
                }
            } finally {
                sizeCtl = sc; //0.75n,下次扩容的阈值
            }
            break;
        }
    }
    return tab;
}

插入值:

public V put(K key, V value) {
	return putVal(key, value, false);
}

/** Implementation for put and putIfAbsent */
final V putVal(K key, V value, boolean onlyIfAbsent) {
    if (key == null || value == null) throw new NullPointerException();
    int hash = spread(key.hashCode()); //获得hash值
    int binCount = 0;
    for (Node<K,V>[] tab = table;;) {
        Node<K,V> f; int n, i, fh;
        if (tab == null || (n = tab.length) == 0) //因为构造函数中没有对node数组初始化,这里判断node数组是不是空的,然后初始化
            tab = initTable();  //初始化node数组
        else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) { //如果node数组已经初始化,但是对应下标位置没有保存元素,直接新建Node对象保存
            if (casTabAt(tab, i, null,
                         new Node<K,V>(hash, key, value, null))) //如果node为null,这赋值的时候要使用CAS保证原子操作,因为这个时候其实没有拿锁,因此可能会有多个线程在进行,只有成功CAS的会返回true。
                break;                   
        }
        else if ((fh = f.hash) == MOVED) //如果当前有其他线程在对node数组进行扩容操作,当前数组会帮忙进行扩容
            tab = helpTransfer(tab, f); //帮忙扩容
        else {
            V oldVal = null;
            synchronized (f) { //锁住对应下标的node
                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)))) { //key已经存在,用新值替换旧值
                                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) //保存的元素大于8,链表转为红黑树
                    treeifyBin(tab, i);
                if (oldVal != null)
                    return oldVal;
                break;
            }
        }
    }
    addCount(1L, binCount); // node数组扩容
    return null;
}

addCount方法中,当node节点下的红黑树数据小与6时,会重新转为链表。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值