目录
一、为什么不推荐使用Hashtable,和HashMap有什么区别
二、既然ConcurrentHashMap能保证线程安全,他是怎么做到的?
3. jdk1.8之后的ConcurrentHashMap的get方法为什么不被阻塞
前言:
我们都知道HashMap不是线程安全的,如果想用线程安全的HashMap,我们可以采用Hashtable(不推荐,性能低)或者ConcurrentHashMap。今天我就来分享一下ConcurrentHashMap的一些个人见解。
一、为什么不推荐使用Hashtable,和HashMap有什么区别
1.为什么不用Hashtable:
首先,我们要知道HashTable它的保证线程安全的方式很粗暴,他就是单纯把自己的方法加Synchronized关键字,我们知道Synchronized是一个重量级锁,开销大;其次,他直接加方法上锁的粒度太粗,性能差。其实官方也不推荐使用了,都推荐使用接下来介绍的ConcurrentHashMap
2.HashTable和HashMap有什么区别:
1.继承体系上看:(先放一张单列集合和双列集合的大图)
粗略的看,我们发现Hashtable和HashMap好像都实现了Map接口,但其实HashMap继承自AbstractMap类(AbstractMap
是 Java 中 Map
接口的抽象基类,它提供了部分 Map
接口的默认实现。AbstractMap
可以帮助你更容易地实现自定义的 Map
类),而Hashtable作为版本弃子,已经被放弃了。(见下图)
HashMap的:
Hashtable的:
2.性能方面:
就之前说的Synchronized实现线程安全,所以导致Hashtable在单线程下不如HashMap,多线程打不过ConcurrentHashMap,真惨啊
3.值设置方面:
HashMap是支持Key(当然key要保证唯一,只能有一个key是null)和value等于null的,而Hashtable的话,会直接报错(NullPointerExecption)也就是空指针异常
HashMap存(null,null)这个键值对,能正常使用:
HashTable的情况:
二、既然ConcurrentHashMap能保证线程安全,他是怎么做到的?
1. jdk1.8之前的实现方式:
此时ConcurrentHashMap是通过一个思想——分段锁 来实现的。可以简单理解为我们把原先的HashMap数组分为了多段(每个段可以理解为一个独立的HashMap),我们如果要操作某个段部分的数据,我们只需对这个段加锁,这样别的段是不受影响的,可以被别的线程所使用。现在我们去对比下之前的HashTable,是不是觉得Hashtable low爆了,所以ConcurrentHashMap并发性能要更高。这还仅仅是1.8之前的版本。
2.jdk1.8之后的实现方式:
那些大佬觉得对之前的实现方式不太满意,分段锁还是粒度太大了,并发性不够。于是他们又整出了一个新的方式。我们了解到HashMap桶下面不是挂红黑树或者链表吗,1.8之后就对这玩意下手了,直接锁住头结点,因为我们不论是链表还是红黑树,你都得从头结点开始查吧,我直接锁住这里就ok了,这样每一个桶相当于之前的分段锁了,粒度更细,并发性能更高。
举个例子:就假设数组是16长度,分段锁分为了4段,1.8之后就直接分为了16段。 比如我要查索引位置=2的桶下的节点,1.8之前就锁住了第一段,而1.8之后只需要锁住桶下的第一个节点就OK了,这样别的15个节点都是允许增删改查的,并发性不就上去了。
当然,这是针对桶下有链表或者红黑树的情况,如果是单纯一个桶,下面没有链表和红黑树,多线程访问,怎么保证这个桶(也就是该索引位置)的线程安全呢?
这时候,操作系统就出手了——CAS(Compare and swap比较并交换,一种乐观锁思想)分段锁,操作系统出手就放心交给他就好了。
简单介绍下CAS:
他有3个参数:1.旧值 old 2.当前读取值 new 3.要更改的值write
我们要做修改,先去读一次得到old,把旧值old带着去写,写的同时判断当前值 new 是否等于之前读到的旧值 old(如果 new=old 表示这期间,这个值没有被动过,我就可以直接操作修改,改为write;如果不等,那我就下次再来)
当然CAS引发的ABA的问题,有兴趣的可以自己去了解一下
3. jdk1.8之后的ConcurrentHashMap的get方法为什么不被阻塞
这个问题我也是参考了别的大佬的回答
我们可以看到,链表转为红黑树之后,他并不是只有红黑树这么一个东西,他其实还备份了一个双向链表,这就保证了get不被阻塞。
比如,我们有一个线程在对图中这个红黑树的节点做增删改查,红黑树在做自平衡,并且,因为写操作会锁住红黑树的根节点,所以我们是不能通过红黑树查数据的,这时候我们其实是通过查双向链表来实现get方法的,所以get方法不会被阻塞。
感兴趣的可以看看这个老哥的博客,ConcurrentHashMap源码这块讲的挺好的: