ConcurrentHashMap

目录

为什么用ConcurrentHashMap而不用HashMap或者HashTable?

哈希算法的两大关键

ConcurrentHashMap中的put方法


 

为什么用ConcurrentHashMap而不用HashMap或者HashTable?

在涉及多线程的时候用ConcurrentHashMap或者HashTable,HashMap不是线程安全的。拿put方法来说HashTable是在整个方法上加锁,而ConcurrentHashMap只在发生冲突时才加锁,这样很大程度上减少了阻塞。

HashTable中的put方法如下:

public synchronized V put(K key, V value)

哈希算法的两大关键

为什么说这个呢?因为ConcurrentHashMap的put方法,实际上就是解决哈希冲突的过程,所以了解下基本概念还是有必要的。两大关键:

  • 散列函数
  • 处理冲突的方法

常见处理冲突的方法:

  • 开放定址法:

6b79791501da447aae38c1542e15096e.png

根据d的不同取值又可以分为线性探测法、平法探测法和双散列法。

  • 拉链法

ConcurrentHashMap中的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; K fk; V fv;
            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)))
                    break;                   // no lock when adding to empty bin
            }
            else if ((fh = f.hash) == MOVED)
                tab = helpTransfer(tab, f);
            else if (onlyIfAbsent // check first node without acquiring lock
                     && fh == hash
                     && ((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;
    }

在put方法中调用了putVal方法,主要逻辑都在这里面。

按行来看代码:

if (key == null || value == null) throw new NullPointerException();

ConcurrentHashMap要求key和value都不能为空,否则抛出异常。

int hash = spread(key.hashCode());
static final int spread(int h) {
    return (h ^ (h >>> 16)) & HASH_BITS;
}

计算哈希值。拿到key的哈希值后,对低16位和高16位进行混合,减少发生碰撞的可能性。比如key.hashCode()为 01010101 01010101 10101010 10101010,无符号右移16位后变成了 00000000 00000000 01010101 01010101,再进行异或后变成了 01010101 01010101 11111111 11111111。HASH_BITS的值为0x7fffffff,即最高位是0,其余位是1。与HASH_BITS想与相当于把最高位变成0,其余位保持不变。

for (Node<K,V>[] tab = table;;) {

进入无限循环,在循环里面插入成功或失败后会返回(即退出循环)。table是ConcurrentHashMap中保存数据的结构。

if (tab == null || (n = tab.length) == 0)
   tab = initTable();

进入无限循环,在循环里面插入成功或失败后会返回(即退出循环)。table是ConcurrentHashMap中保存数据的结构。

else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
    if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value)))
        break;                   // no lock when adding to empty bin
}

(n - 1) & hash把哈希值转换为数组下标(或者说把哈希值限定在数组范围之内)。tabAt(tab, i = (n - 1) & hash)找出当前位置的Node值,如果是null,那就说明没有发生冲突,可以进行插入操作。casTabAt(tab, i, null, new Node<K,V>(hash, key, value))cas算法插入新的结点。cas算法原理:获取当前内存的位置,还有一个期望值,如果相等,表示没有其他线程进行修改,可以插入,不然不做任何操作。如果发生了冲突,那么接着往下走。

else if ((fh = f.hash) == MOVED)
   tab = helpTransfer(tab, f);

MOVED是一个特殊的标记,表示正在进行扩容。

else if (onlyIfAbsent // check first node without acquiring lock
         && fh == hash
         && ((fk = f.key) == key || (fk != null && key.equals(fk)))
         && (fv = f.val) != null)
    return fv;

onlyIfAbsent 表示只进行检查不进行插入操作,直接返回当前冲突结点的value。

V oldVal = null;
synchronized (f) {

首先定义一个oldVal,因为最终要返回发生冲突的节点的旧值。然后终于加上了锁。

binCount = 1;

binCount表示发生冲突的次数。

if (e.hash == hash &&
    ((ek = e.key) == key ||
    (ek != null && key.equals(ek)))) {
        oldVal = e.val;
        if (!onlyIfAbsent)
            e.val = value;
        break;
}

如果插入结点的key和hash值与当前结点的key和hash值一致,那么更新当前结点的value。

Node<K,V> pred = e;
if ((e = e.next) == null) {
      pred.next = new Node<K,V>(hash, key, value);
      break;
}

e = e.next用线性探测法探测下一个结点。

	
else if (f instanceof TreeBin)

如果是红黑树,那么插入红黑树。

if (binCount >= TREEIFY_THRESHOLD)
    treeifyBin(tab, i);

如果冲突次数超过了指定的值,那么把链表转化为红黑树。

addCount(1L, binCount);

容量加1。

 

简单总结就是:没有冲突,则直接插入,返回null;发生了冲突,返回key一致,那么更新为新的value,返回旧value,如果key不一致,探测下一个位置,再重复循环上面这些步骤。

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值