• HashMap、ConcurrentHashMap以及HashTable(面试向)


    ---->HashMap

       在java1.7中,hashmap的数据结构是基于数组+链表的结构,即我们比较熟悉的Entry数组,其包含的(key-value)键值对的形式。在多线程环境下,HashMap进行put操作会引起死循环,是因为多线程会导致HashMap的Entry链表形成环形数据结构,一旦形成环形数据结构,Entry的next节点永远不为空,就会产生死循环获取Entry。

       hashmap实现原理参考

       Entry是HashMap中的一个静态内部类。代码如下

     static class Entry<K,V> implements Map.Entry<K,V> {
            final K key;
            V value;
            Entry<K,V> next;//存储指向下一个Entry的引用,单链表结构
            int hash;//对key的hashcode值进行hash运算后得到的值,存储在Entry,避免重复计算
    
            /**
             * Creates new entry.
             */
            Entry(int h, K k, V v, Entry<K,V> n) {
                value = v;
                next = n;
                key = k;
                hash = h;
            } 

      在java1.8中,hashmap是以 数组+链表+红黑树,由于有红黑树的加入,hashmap性能有了很大程度的优化,但是还是没办法解决在并发环境下的线程安全。

    ---->HashTable

       hashtabled 和 hashmap 的实现原理几乎一样,差别在于

    • HashMap的键和值都允许有null值存在,而HashTable则不行
    • HashMap是非线程安全的,HashTable是线程安全的
    • 在单线程环境下,HashMap的运行效率是要比HashTable要快得多的(因为HashTable是线程安全,但是其实现的安全的策略牺牲代价太大,get/put所有相关操作都是synchronized的,相当于给整个哈希表加了一个大锁,多线程访问时候,只要有一个线程访问或操作该对象时,则其他线程就只能阻塞,相当于将所有的操作串行化)
    • Hashtable默认的初始大小为11,之后每次扩充,容量变为原来的2n+1。HashMap默认的初始化大小为16。之后每次扩充,容量变为原来的2倍
    • HashMap的Iterator是fail-fast迭代器。当有其它线程改变了HashMap的结构(增加,删除,修改元素),将会抛出ConcurrentModificationException。不过,通过Iterator的remove()方法移除元素则不会抛出ConcurrentModificationException异常。但这并不是一个一定发生的行为,要看JVM。JDK8之前的版本中,Hashtable是没有fast-fail机制的。在JDK8及以后的版本中 ,HashTable也是使用fast-fail的。

     ---->ConcurrentHashMap

        在java1.7中,concurrenthashmap的数据结构为 Segment + HashEntryConcurrentHashMap锁分段技术:假如容器里有多把锁,每一把锁用于锁容器其中一部分数据,那么当多线程访问容器里不同数据段的数据时,线程间就不会存在锁竞争,从而可以有效的提高并发访问效率,这就是ConcurrentHashMap所使用的锁分段技术。首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。用一个Segment数组维护所有的键值对,一个Segment对象的数据结构相当于一个HashMap,即内部拥有一个Entry数组,数组中的每个元素又是一个链表;同时又是一个ReentrantLock(Segment继承了ReentrantLock)。ConcurrentHashMap中的HashEntry相对于HashMap中的Entry有一定的差异性:HashEntry中的value以及next都被volatile修饰,这样在多线程读写过程中能够保持它们的可见性,代码如下:

    static final class HashEntry<K,V> {
            final int hash;
            final K key;
            volatile V value;
            volatile HashEntry<K,V> next;

         ConcurrentHashMap不允许Key或者Value的值为NULL

     在java1.8中,它摒弃了Segment(锁段)的概念,而是启用了一种全新的方式实现,利用CAS算法。它沿用了与它同时期的HashMap版本的思想,底层依然由“数组”+链表+红黑树的方式思想,接采用transient volatile Node<K,V>[] table保存数据,采用table数组元素作为锁,从而实现了对每一行数据进行加锁,进一步减少并发冲突的概率

     并且,ConcurrentHashMap相对于HashTable来说,ConcurrentHashMap的很多操作比如get,clear,iterator 都是弱一致性的,而HashTable是强一致性的。

        何为弱一致性?

        get方法是弱一致的,是什么含义?可能你期望往ConcurrentHashMap底层数据结构中加入一个元素后,立马能对get可见,但ConcurrentHashMap并不能如你所愿。换句话说,put操作将一个元素加入到底层数据结构后,get可能在某段时间内还看不到这个元素,若不考虑内存模型,单从代码逻辑上来看,却是应该可以看得到的。

        因为没有全局的锁,在清除完一个segments之后,正在清理下一个segments的时候,已经清理segments可能又被加入了数据,因此clear返回的时候,ConcurrentHashMap中是可能存在数据的。因此,clear方法是弱一致的。如下:

    public void clear() {
        for (int i = 0; i < segments.length; ++i)
            segments[i].clear();
    }

         ConcurrentHashMap的迭代器底层原理中,在遍历过程中,如果已经遍历的数组内容发生了变化,迭代器不会抛出ConcurrentModificationException异常。如果未遍历的数组上的内容发生了变化,则有可能反映到迭代过程中。这就是ConcurrentHashMap迭代器弱一致的表现。

        参考:ConcurrentHashMap能完全替代HashTable吗?

        参考:ConcurrentHashMap总结

  • 相关阅读:
    js 格式化时间
    javascript Base64 加密解密方法
    为什么给元素添加了z-index却没有效果
    浮动 与 清除浮动
    Vue 介绍 以及 学习总结(这里不详细展开)
    redux 的基础用法
    c# 几种深拷贝方式的比较
    C#四种深拷贝方法
    Innershar C#中Skip和Take的用法
    DataTable.AcceptChanges方法有何用处
  • 原文地址:https://www.cnblogs.com/liangyueyuan/p/9743652.html
Copyright © 2020-2023  润新知