浅谈 HashMap,Hashtable,ConcurrentHashMap

浅谈 HashMap,Hashtable,ConcurrentHashMap
相同点
这3个集合都是采用key-value的数据结构存储数据。
java 1.8之前底层采用数组+链表的形式 (链表是为了解决hash冲突),java1.8及以后采用数组+链表+红黑树的新式(当长度大于等于8时有链表转成红黑树,使用红黑树以加快检索速度)。
不同点
1, HashMap和ConcurrentHashMap都是允许null的键值对存在,Hashtable不允许
2,初始化大小及扩容不一致
a:初始化大小
HashMap和ConcurrentHashMap 初始化大小都是16(1<<4),Hashtable 初始化大小11 (个人觉得不是很合理因为位运算1<<4的计算效率会更高,而且从命名上看也不符合java的命名规范(java遗留问题Hashtable在java命名规范前已经定义后续版本都未修复) 所以这个集合基本很少使用 嘻嘻…)。
b:扩展
HashMap 扩容 newCap = oldCap << 1 ,扩容原来的2N,ConcurrentHashMap同样扩容到原来的2N;
Hashtable扩容newCapacity = (oldCapacity << 1) + 1; 扩容原来的2N+1;
3,线程安全问题(重点讲解)
HashMap :线程不安全的 ,ConcurrentHashMap,Hashtable 线程安全的
java 1.7之前
ConcurrentHashMap使用分段锁来保证在多线程下的性能。ConcurrentHashMap中则是一次锁住一个桶。ConcurrentHashMap默认将hash表分为16个桶,诸如get,put,remove等常用操作只锁当前需要用到的桶。这样,原来只能一个线程进入,现在却能同时有16个写线程执行,并发性能的提升是显而易见的。
Hashtable 采用synchronized来保证线程安全,其本质就是在对方法加锁:public synchronized V put(K key, V value)。
不管是分段锁还是对方法加锁在高并发下都存在一定的效率问题,因此java1.8对 ConcurrentHashMap做了优化
查看ConcurrentHashMap源码已put方法为例:
public V put(K key, V value) {
return putVal(key, value, false); //调用putVal方法跟踪putVal方法
}

putVal方法源码
 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;
        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, null))) //1:看这里 casTabAt
                break;                   // no lock when adding to empty bin
        }
        else if ((fh = f.hash) == MOVED)
            tab = helpTransfer(tab, f);
        else {
            V oldVal = null;
            synchronized (f) { //2: 同步锁
                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, null);
                                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;
                        }
                    }
                }
            }
            if (binCount != 0) {
                if (binCount >= TREEIFY_THRESHOLD)
                    treeifyBin(tab, i); //这里是判断转成红黑树
                if (oldVal != null)
                    return oldVal;
                break;
            }
        }
    }
    addCount(1L, binCount);
    return null;
}

从备注1,2两点可以看出来 java1.8及以后对 ConcurrentHashMap 采用了cas(乐观锁,下次再讨论cas相关问题),和synchronized相结合的方式来保证线程安全。
注: 多线程下对HashMap 操作还会出现死循环吗?
java1.7 会 因为扩容时会从链表头部插入数据。
java1.8 不会 1.8优化了插入规则从链表尾部插入数据。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值