Hashtatble、HashMap、ConcurrentHashMap区别

4 篇文章 0 订阅
2 篇文章 0 订阅

Hashtable和HashMap的区别

命名

Hashtable命名没有遵循驼峰命名,HashMap命名遵循了驼峰命名

继承类

Hashtable继承的是Dictionary类,HashMap继承的是AbstractMap类

线程

Hashtable在多线程的情况下是线程安全的,因为Hashtable在数据操作的方法上都加上了synchronized关键字。

 //Hashtable的put方法
public synchronized V put(K key, V value) 

HashMap没有实现线程安全,在多线程的状态下可能会造成环形链表和轮训。

初始化和扩容

Hashtable的默认初始化容量为11,默认加载因子是0.75f,扩容是基础容量翻一倍+1

 public Hashtable() {
        this(11, 0.75f);
    }

HashMap的默认初始化容量为16,默认加载因子是0.75f,扩容是基础容量翻一倍

失败机制

Hashtable是安全失败机制(fail-safe),HashMap是快速失败机制(fail-fast)。

快速失败机制

在map使用迭代器进行遍历时,对元素进行删除,添加的操作时,会抛出java.util.ConcurrentModificationException异常。

java.util包下的集合类都是快速失败的。

 HashMap<Integer,Integer> map = new HashMap();
        map.put(1,1);
        map.put(2,2);
        map.put(3,3);
        map.put(4,4);
        map.put(5,5);
        map.put(6,6);

        Set<Integer> set = map.keySet();

        Iterator<Integer> it = set.iterator();

         while(it.hasNext()){
            Integer key = it.next();
            map.put(key+10,22);
        }

//Exception in thread "main" java.util.ConcurrentModificationException
	at java.util.HashMap$HashIterator.nextNode(HashMap.java:1442)
	at java.util.HashMap$KeyIterator.next(HashMap.java:1466)
	at RBTree.Test.main(Test.java:29)

安全失败机制

java.util.concurrent包下的集合类都是安全失败的

线程A正在遍历Hashtable,线程B此时进行修改,删除,添加的操作,线程A是拿不到最新的数据的,因为采用安全失败机制的容器先复制原有集合内容,在拷贝的集合上进行遍历。


HashMap在JDK1.7和1.8的区别

底层结构

1.7数组+链表

1.8数组+链表+红黑树。考虑到在数据量大的情况下导致某一桶的链表过长,使查询速度变慢的情况,加入的红黑树。当桶的长度大于64且链表长度大于8的时候,就会将链表转化为红黑树,如果链表长度小于等于6,红黑树又会退化为链表。

插入法

1.7头插法,可能会形成环,造成死循环。

1.8尾插法

扩容

1.7会颠倒链表的顺序;1.7则是在元素插入前。

1.8会保持原链表的顺序,而且1.8是在元素插入后检测是否需要扩容。


ConcurrentHashMap

实现线程安全的原理

JDK1.7 segment+ReentrantLock+HashEntry

通过将一个整体的桶分成一个个segment,桶和entry里的key,value都实现了volatile关键字,volatile关键字可以保证数据的有序性可见性,也就是一个线程对数据进行了修改,其他线程也能立刻看到最新的数据。所以ConcurrentHashMap在多线程情况下get()方法不需要添加synchronized关键字也能保证线程安全,并且效率高。

在进行put()时,当前线程占据当前segment的锁,其余的线程会进行自旋,期间如果线程空出,那就会抢锁。当自旋达到一定次数依旧没有获得锁,就会进入阻塞队列。

private HashEntry<K,V> scanAndLockForPut(K key, int hash, V value) {  
            HashEntry<K,V> first = entryForHash(this, hash);  
            HashEntry<K,V> e = first;  
            HashEntry<K,V> node = null;  
            int retries = -1; // negative while locating node  
            while (!tryLock()) {  
                HashEntry<K,V> f; // to recheck first below  
                if (retries < 0) {  
                    if (e == null) {  
                        if (node == null) // speculatively create node  
                            node = new HashEntry<K,V>(hash, key, value, null);  
                        retries = 0;  
                    }  
                    else if (key.equals(e.key))  
                        retries = 0;  
                    else  
                        e = e.next;  
                }  
                else if (++retries > MAX_SCAN_RETRIES) {  
                    lock();  
                    break;  
                }  
                else if ((retries & 1) == 0 &&  
                         (f = entryForHash(this, hash)) != first) {  
                    e = first = f; // re-traverse if entry changed  
                    retries = -1;  
                }  
            }  
            return node;  
        }  

 

JDK1.8 CAS+synchronized+node

 在JDK1.8中,弃用了segment,将hashEntry换成了node,好处是降低了锁的粒度,减少了并发冲突。原来需要锁整块segment,现在只需要锁头部node。在put()时,判断节点为null就利用CAS插入,失败就自旋保证成功。在桶长度>64且链表长度大于8时同样会转化为红黑树。

 CAS

        t1线程拿到执行权限,t2线程被告知失败。t1和t2都各自存有int v = 20这个共享变量的副本V1,V2,t1线程会把副本值V1和预期值E1比较,如果V1=E1,则更新变量V1=21,并将V1写入主内存,v = 21。此时t2抢到执行权限,比较V2和E2,发现不相等,更新失败,重新更新副本,最后写入成功。

        其实现方式是基于硬件平台的汇编指令,在intel的CPU中,使用的是cmpxchg指令,也就是说CAS是靠硬件实现的,从而在硬件层面提升效率。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值