一:HashMap
HashMap 本身不是线程安全的,在多线程的环境下,使用哈希表可以使用:Hashtable,ConcurrentHashMap.
二:Hashtable
Hashtable只是在每个方法上加上synchronized,相当于直接针对Hashtable对象加锁(this加锁),两个线程访问Hashtable中的任意数据都会出现锁竞争;
(1)如果多线程访问同一个Hashtable就会直接造成锁冲突
(2)size 属性也是通过synchronized来控制同步,也是比较慢的.
(3)一旦触发扩容,就由该线程完成整个扩容过程,这个过程会涉及到大量的元素拷贝,效率非常低.
三:ConcurrentHashMap
(1):读操作没有加锁(但使用了volatile保证从内存中读取结果),只对写操作加锁,加锁的方式仍然是用synchronized,但不是锁整个对象,而是"锁桶",(用每个链表的头节点作为锁对象),大大降低了锁冲突的概率.
如果两个线程,针对不同的链表进行操作,是不会涉及到锁冲突的(本身,操作不同链表上的元素,也没有修改"公共变量",就不会涉及到线程安全)
收益是非常大的,一个hash表中,"桶"的数量 是非常多的,大量的操作,都不涉及到"锁冲突"了,synchronized就是偏向锁了
(2):充分利用CAS的特性,比如size属性通过CAS来来更新,避免出现重量级锁的情况.
(3)优化了扩容方法:化整为零.
扩容是非常重量的操作,平时的每次put,假设1us,触发扩容的这一次put,执行了1000ms,对于使用时有非常大的影响
ConcurrentHashMap会在扩容的时候,申请一个新的数组,这样就有两份空间,一份是扩容之前的空间,一份是扩容之后的空间,接下来每次进行hash表的基本操作,都会把一部分数据从旧空间搬运到新空间,而不是一次性搬完,
搬的过程中:
插入:插入到新的数组中;
删除:新的旧的都要删,
查找:新的旧的都要查找.
jdk 8之前,ConcurrentHashMap基于分段锁的方式来实现的:引入若干个锁对象,每个锁对象管理若干个Hash桶,相对于Hashtable是进化,但不如直接锁桶,代码写起来更复杂.
四:总结
HashMap:线程不安全,key允许为null
Hashtable :线程安全,使用synchronized 锁Hashtable对象,效率较低,key不允许为null
ConcurrentHashMap:线程安全,使用synchronized 锁每个链表头结点,锁冲突降低,充分利用CAS机制,优化了扩容方法,key不允许为null.