ConcurrentHashMap & HashTable

HashMap在多线程环境下存在线程安全问题,那你一般都是怎么处理这种情况的?

Collections.synchronizedMap(Map)创建线程安全的map集合;
Hashtable
ConcurrentHashMap

Collections.synchronizedMap实现线程安全的你有了解过么?

SynchronizedMap有一个对象Object mutex作为锁对象,所有集合方法里都添加了synchronized(锁对象){添加语句},由于所有方法共用一把锁,效率低。
在SynchronizedMap(Map<K,V> m)构造方法中,传入HashMap对象,那么锁对象就是传入的Map。

说说ConcurrentHashMap的数据结构吧,以及为啥他并发度这么高?

1.7版本,使用的是分段锁,将整个ConcurrentHashMap数组分为多个segment,为每个segment上一把锁,由于segment之间的锁不一样,所以不会互相干扰,由原来的锁住了整张表,变为了锁注表中的某一段,可以并法的执行每个段。仍然是数组加链表。
在这里插入图片描述
Segment是ConcurrentHashMap 的一个内部类,继承了ReentrantLock。Segment内部维护的是HashEntry<K,V>[]数组,被volatile修饰,保证了可见性。

每次插入元素时,先定位到segment,再用segment的put操作:

public V put(K key, V value) {
    Segment<K,V> s;
    if (value == null)
        throw new NullPointerException();//这就是为啥他不可以put null值的原因
    int hash = hash(key);
    int j = (hash >>> segmentShift) & segmentMask;
    if ((s = (Segment<K,V>)UNSAFE.getObject          
         (segments, (j << SSHIFT) + SBASE)) == null) 
        s = ensureSegment(j);
    return s.put(key, hash, value, false);
}

Segment的put

final V put(K key, int hash, V value, boolean onlyIfAbsent) {
          // 将当前 Segment 中的 table 通过 key 的 hashcode 定位到 HashEntry
            HashEntry<K,V> node = tryLock() ? null :
                scanAndLockForPut(key, hash, value);
            V oldValue;
            try {
                HashEntry<K,V>[] tab = table;
                int index = (tab.length - 1) & hash;
                HashEntry<K,V> first = entryAt(tab, index);
                for (HashEntry<K,V> e = first;;) {
                    if (e != null) {
                        K k;
 // 遍历该 HashEntry,如果不为空则判断传入的 key 和当前遍历的 key 是否相等,相等则覆盖旧的 value。
                        if ((k = e.key) == key ||
                            (e.hash == hash && key.equals(k))) {
                            oldValue = e.value;
                            if (!onlyIfAbsent) {
                                e.value = value;
                                ++modCount;
                            }
                            break;
                        }
                        e = e.next;
                    }
                    else {
                 // 不为空则需要新建一个 HashEntry 并加入到 Segment 中,同时会先判断是否需要扩容。
                        if (node != null)
                            node.setNext(first);
                        else
                            node = new HashEntry<K,V>(hash, key, value, first);
                        int c = count + 1;
                        if (c > threshold && tab.length < MAXIMUM_CAPACITY)
                            rehash(node);
                        else
                            setEntryAt(tab, index, node);
                        ++modCount;
                        count = c;
                        oldValue = null;
                        break;
                    }
                }
            } finally {
               //释放锁
                unlock();
            }
            return oldValue;
        }

put

每次插入元素时,先定位到segment,再用segment的put操作:

put时,尝试获取锁,如果获取失败肯定就有其他线程存在竞争,则利用 scanAndLockForPut() 自旋获取锁。

尝试自旋获取锁。
如果重试的次数达到了 MAX_SCAN_RETRIES 则改为阻塞锁获取,保证能获取成功。

get

get 逻辑比较简单,只需要将 Key 通过 Hash 之后定位到具体的 Segment ,再通过一次 Hash 定位到具体的元素上。因为只读不修改,所以get不会加锁。

缺点

1.查询的时候,还得遍历链表,会导致效率很低
2.锁太多,结构很重
2.不断加锁释放锁,效率低

1.8优化

抛弃了原有的 Segment 分段锁,而采用了 CAS + synchronized 来保证并发安全性。

put时,用CAS尝试写入,失败则自旋保证成功。现在的值和想要的值(null)是否相等,即为空则插入新数据。casTabAt(tab, i , null, new Node<K,V>(hash, key, value, null))
如果还是失败,则用synchronize锁写入数据
在这里插入图片描述

CAS是什么,自旋又是什么?

CAS是一个乐观锁,其实没有加锁,每次操作数据前,判断当前值和以前的值是否相等,相等则说明没有被修改过,不相等则说明被修改过,则那我只有从头再将原来执行过的代码执行一遍,得到正确的数据。当执行到对数据操作时,继续判断是否被修改过。

自旋则是一直循环执行了。

ABA问题。加版本号和时间戳。

在这里插入图片描述

get:
根据计算出来的 hashcode 寻址,如果就在桶上那么直接返回值。
如果是红黑树那就按照树的方式获取值。
就不满足那就按照链表的方式遍历获取值。

谈谈你理解的 Hashtable,讲讲其中的 get put 过程。ConcurrentHashMap同问。1.8 做了什么优化?线程安全怎么做的?不安全会导致哪些问题?如何解决?有没有线程安全的并发容器?ConcurrentHashMap 是如何实现的? ConcurrentHashMap并发度为啥好这么多?1.7、1.8 实现有何不同?为什么这么做?CAS是啥?ABA是啥?场景有哪些,怎么解决?synchronized底层原理是啥?synchronized锁升级策略快速失败(fail—fast)是啥,应用场景有哪些?安全失败(fail—safe)同问。

作者:敖丙
链接:https://zhuanlan.zhihu.com/p/97902016
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值