HashMap,Vector,Hashtable,ConcurrentHashMap等并发容器线程安全的进化过程

本文详细介绍了Java集合框架中线程安全的相关知识,包括Vector、Collections.synchronizedList和synchronizedMap的线程安全实现,HashMap在JDK1.7和1.8的扩容及解决哈希冲突的策略,以及ConcurrentHashMap的并发控制和优化。此外,还提到了CopyOnWriteArrayList的特性及其适用场景,以及ConcurrentLinkedQueue作为高效并发队列的实现。文章深入剖析了各种集合类在多线程环境下的行为和性能,并探讨了数据结构与并发安全的平衡。

一 概述

二 Vector

Vector线程安全的集合类,同时它是基于动态数组实现的集合类,借助Synchronized修饰的方法来保证多线程安全。在Vector集合类中Synchronized加在方法上,性能会比较差。

三 通过Collections中的指定方法保证线程安全

ArrayList和HashMap为线程不安全的集合类,我们可以通过Collections中提供的方法使得其能够在多线程的情况下保持线程安全。

Collections.synchronizedList(new ArrayLixt<E>)

    //借助同步锁synchronized (mutex)声明同步代码块实现多线程安全
    public static <T> List<T> synchronizedList(List<T> list) {
        return (list instanceof RandomAccess ?
                new SynchronizedRandomAccessList<>(list) :
                new SynchronizedList<>(list));
    }

    SynchronizedRandomAccessList(List<E> list, Object mutex) {
            super(list, mutex);
        }

    SynchronizedList(List<E> list, Object mutex) {
            super(list, mutex);
            this.list = list;
        }

Collections.synchronizedMap(new HashMap<K,V>)

    //通过同步锁synchronized(mutext)声明同步代码块保证线程安全
    public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m) {
        return new SynchronizedMap<>(m);
    }

    private static class SynchronizedMap<K,V>
        implements Map<K,V>, Serializable {
        private static final long serialVersionUID = 1978198479659022715L;

        private final Map<K,V> m;     // Backing Map
        final Object      mutex;        // Object on which to synchronize

        SynchronizedMap(Map<K,V> m) {
            this.m = Objects.requireNonNull(m);
            mutex = this;
        }

        SynchronizedMap(Map<K,V> m, Object mutex) {
            this.m = m;
            this.mutex = mutex;
        }
     }

由源码可知,SynchronizedList和SynchronizedMap都是借助同步代码块实现使之变得线程安全,所以这种方式性能问题并未得到很好的解决。

四 Map接口

                                

HashMap

根据key的hashCode进行存储,由于可以直接计算出hashCode值,所以可以直接定位对应值的位置,所以反问速度是很快的,它允许一个key为null,当key为对象的时候,该对象需要重写hashCode()方法和equals()方法保证对象的唯一性,从而满足HashMap中key的唯一性,而HashMap的value是没有任何限制。

在JDK1.7和JDK1.8中的HashMap的默认初始容量为16,loadFactor均为0.75。

JDK1.7中HashMap扩容的源码

    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

    static final float DEFAULT_LOAD_FACTOR = 0.75f;

    //扩容
    void resize(int newCapacity) {//传入新的容量  
        Entry[] oldTable = table;//引用扩容前的Entry数组  
        int oldCapacity = oldTable.length;  
        if (oldCapacity == MAXIMUM_CAPACITY) {//扩容前的数组大小如果已经达到最大(2^30)了  
            threshold = Integer.MAX_VALUE;//修改阈值为int的最大值(2^31-1),这样以后就不会扩容了  
            return;  
        }  
  
        Entry[] newTable = new Entry[newCapacity];//初始化一个新的Entry数组  
        //table重新hash到新table上
        transfer(newTable, initHashSeedAsNeeded(newCapacity));//将数据转移到新的Entry数组中  
        table = newTable;//HashMap的table属性引用新的Entry数组  
        threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);//修改阈值  
    }  

    static class Entry<K,V> implements Map.Entry<K,V> {
        final K key;
        V value;
        Entry<K,V> next;
        int hash;

        /**
         * Creates new entry.
         */
        //h=hash值,k=新元素key,v=新元素的value,n=原链表
        Entry(int h, K k, V v, Entry<K,V> n) {
            value = v;
            next = n;//next指向了原链表,新元素插入到了原链表头部
            key = k;
            hash = h;
        }

    //扩容后转移数据
    void transfer(Entry[] newTable, boolean rehash) {
        int newCapacity = newTable.length;
        for (Entry<K,V> e : table) { //遍历旧的Entry数组
            while(null != e) {
                //造成死循环的关键代码(截图中的步骤一)
                Entry<K,V> next = e.next;
                if (rehash) {
                    e.hash = null == e.key ? 0 : hash(e.key);
                }
                int i = indexFor(e.hash, newCapacity); //重新计算每个元素在数组中的下标位置  
                e.next = newTable[i];  //由于采用头插法,链表顺序颠倒(newTable[i]=next)
                newTable[i] = e;
                e = next;
            }
        }
    }

JDK 1.7 HashMap的数据结构

在JDK1.7中HashMap的数据结构为数组 + 链表

    public V put(K key, V value) {
        if (table == EMPTY_TABLE) {
            inflateTable(threshold);
        }
        if (key == null)
            return putForNullKey(value);
        //通过key计算hash值
        int hash = hash(key);
        //通过hash值的计算数组下标
        int i = indexFor(hash, table.length);
        //遍历该下标位置的链表,如果有存在相同的key,就会进行替换
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
        //没有相同的key,就需要插入元素,修改次数+1
        modCount++;
        //添加元素
        addEntry(hash, key, value, i);
        return null;
    }

    void addEntry(int hash, K key, V value, int bucketIndex) {
        if ((size >= threshold) && (null != table[bucketIndex])) {
            //如果size大于阈值后就会扩容成原来的两倍
            resize(2 * table.length);
            hash = (null != key) ? hash(key) : 0;
            bucketIndex = indexFor(hash, table.length);
        }

        createEntry(hash, key, value, bucketIndex);
    }

    void createEntry(int hash, K key, V value, int bucketIndex) {
        Entry<K,V> e = table[bucketIndex];
        table[bucketIndex] = new Entry<>(hash, key, value, e);
        size++;
    }

     static class Entry<K,V> implements Map.Entry<K,V> {
        final K key;
        V value;
        Entry<K,V> next;
        int hash;

        /**
         * Creates new entry.
         */
        Entry(int h, K k, V v, Entry<K,V> n) {
            value = v;
            next = n; //next指向了原链表,新元素插入到原链表头部
            key = k;
            hash = h;
        }

JDK1.7解决hash碰撞(hash冲突)问题

向HashMap中插入数据时,先根据key进行Hash运算【int h = hash(key)】获取key的hash值h,然后根据方法indexFor()将hash值h与数组的长度进行逻辑与运算【int i = indexFor(hash, table.length)】从而得到数组下标。

   static int indexFor(int h, int length) {
        // assert Integer.bitCount(length) == 1 : "length must be a
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值