Java集合解析:hashmap在多线程下的替代方案

hashMap虽然效率好,但是多线程下是不安全的。一般在多线程的场景,有几种不同的方式去代替:

  • Collections.synchronizedMap(Map)创建线程安全的map集合
  • Hashtable
  • ConcurrentHashMap:性能和效率好

详细解析
Collections.synchronizedMap(Map)

private static class SynchronizedMap<K,V> implements Map<K,V>, Serializable {
        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;
        }
        public V put(K key, V value) {
            synchronized (mutex) {return m.put(key, value);}
        }
        public V remove(Object key) {
            synchronized (mutex) {return m.remove(key);}
        }
        ...

HashTable
线程安全,但是效率低,因为是synchronized方法。

    public Hashtable(int initialCapacity) {
        this(initialCapacity, 0.75f);
    }

    /**
     * Constructs a new, empty hashtable with a default initial capacity (11)
     * and load factor (0.75).
     */
    public Hashtable() {
        this(11, 0.75f);
    }
	public synchronized V put(K key, V value) {
		...
	}


hashtable和hashmap不同点

HashMaoHashTable
允许key/value为空YesNo(多线程环境下,无法判断null是没有元素,还是该元素的key是null)
初始容量1611
扩容机制容量翻倍容量翻倍+1

ConcurrentHashMap

  1. JDK 1.8 采用 数组+链表(红黑树)
  2. 放弃了分段锁,采用CAS(乐观锁) + synchronized 来保证并发安全性,对每个数组元素加锁。
  3. 链表节点个数大于8,转化为链表
    在这里插入图片描述
static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        //采用volatile保证可见性
        volatile V val;
        volatile Node<K,V> next;

        Node(int hash, K key, V val, Node<K,V> next) {
            this.hash = hash;
            this.key = key;
            this.val = val;
            this.next = next;
        }
}

put

/** Implementation for put and putIfAbsent */
final V putVal(K key, V value, boolean onlyIfAbsent) {
	//根据key计算计算hash值
    int hash = spread(key.hashCode());
    int binCount = 0;
    //一直循环,直到插入成功
    for (Node<K,V>[] tab = table;;) {
        Node<K,V> f; int n, i, fh;
        
        if (tab == null || (n = tab.length) == 0)
        //第一次时,初始化table
            tab = initTable();
        else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
        //如果该位置的Node还未初始化,则通过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)
        //table[i]的节点的hash等于MOVED,则说明正在扩容,则帮助其扩容
            tab = helpTransfer(tab, f);
        else {
        //tab[i]有元素,发生了碰撞
            V oldVal = null;
            //如果相应位置的Node不为空,且当前该节点不处于移动状态,则对该节点加synchronized锁(对链表头结点加锁)
            synchronized (f) {
                if (tabAt(tab, i) == f) {
                //如果该节点的hash不小于0,则遍历链表更新节点或插入新节点;
                    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;
                            }
                        }
                    }
                    //如果是红黑树结构,则通过putTreeVal方法往红黑树中插入节点;
                    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) {
            //节点大于等于8,将链表转化为红黑树
                if (binCount >= TREEIFY_THRESHOLD)
                    treeifyBin(tab, i);
                if (oldVal != null)
                    return oldVal;
                break;
            }
        }
    }
    //插入新节点,更新元素个数
    addCount(1L, binCount);
    return null;
}

记录size

/**
 * Base counter value, used mainly when there is no contention,
 * but also as a fallback during table initialization
 * races. Updated via CAS.
 */
private transient volatile long baseCount;

当插入新数据或则删除数据时,会通过addCount()方法更新baseCount。用volatile,没有额外加锁,弱一致性。


版权声明:本文为zd836614437原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。