ConcurrentHashMap原理解析

ConcurrentHashMap是HashMap在多线程下使用方式,采用分段锁原理。在JDK7和JDK8中实现原理不同, JDK7中使用标准分段锁,默认为16段,可设置,一旦设置不可修改,锁粒度大,通过ReentrantLock实现锁。JDK8中通过使用CAS和synchronize关键字实现,本文我们通过源码来解析ConcurrentHashMap的原理。

数据结构

ConcurrentHashMapd的组成是有数组+链表+红黑树实现的,JDK8中HashMap和ConcurrentHashMap结构类似:

  • 根据key取hash值,然后按位与运算,找到数组的位置
  • 如果对应位置没有元素,则直接放入数组中;
  • 如果有元素,并且是链表,加入链表尾部;
  • 如果是红黑树,则存入红黑树,并做平衡处理
    在这里插入图片描述
    转成红黑树的目的就是为了解决链表长度过大, 每次遍历查询时间过长, 提升了效率

几个重要参数

// 默认初始化容量
private static final int DEFAULT_CAPACITY = 16;
// 加载因子 当Map中元素个数超过容量的0.75倍后,开始扩容
private static final float LOAD_FACTOR = 0.75f;
// 链表元素超过8个后转为红黑树
static final int TREEIFY_THRESHOLD = 8;
// 链表元素个数低于6个后转为链表
static final int UNTREEIFY_THRESHOLD = 6;

static final int MIN_TREEIFY_CAPACITY = 64;

Node为数组中存放的元素,结构如下

static class Node<K,V> implements Map.Entry<K,V> {
    final inthash;
    final K key;
    volatile V val;
    volatile Node<K,V> next;
	...
    public final int hashCode()   { return key.hashCode() ^ val.hashCode(); }
    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;
    }
}

构造方法

public ConcurrentHashMap(int initialCapacity) {
    if(initialCapacity < 0)
        throw new IllegalArgumentException();
    // 初始化容量会设置为与initialCapacity的2倍相近的2的次幂的值, 如果前后相等, 取后值
    int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ?
               MAXIMUM_CAPACITY :
               tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));
    this.sizeCtl = cap;
}

Hash

使用key自己的hash值, 然后spread转换: 使用hash值与高16异或运算后再和32位全是1做与运算, 目的保留高位特性, 降低hash碰撞概率

static final int spread(int h) {
   return (h ^ (h >>> 16)) & HASH_BITS;
}

get获取元素

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值计算元素所在数组位置
	// table 不是空 所在位置也不是空
	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;
}

put添加元素

final V putVal(K key, V value, boolean onlyIfAbsent) { // onlyIfAbsent 仅在不存在时存入元素, 如果存在返回原值
	// 元素kv都不能为空
	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)
			tab = initTable(); // 初始化map
		else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) { // 元素应在数组位置为空
			// 使用CAS更换元素
			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) // map正在发生扩容
			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) {
				// 如果元素不存在, 并且是链表, 遍历链表时binCount在增加, 如果大于8, 转为红黑树
				if (binCount >= TREEIFY_THRESHOLD)
					treeifyBin(tab, i); // 转为红黑树
				if (oldVal != null) // 如果是覆盖旧元素, 返回旧元素
					return oldVal;
				break;
			}
		}
	}
	addCount(1L, binCount); // 检查是否需要扩容
	return null;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值