一、HashMap
HashMap不适用在多线程里面,因为其本身不带任何锁,多线程操作时是不安全的,而相对安全的HashTable和ConcurrentHashMap是基于HashMap的基础上,通过一定的方式加锁以及相关的优化而得来的。
二、HashTable
HashTable加锁的方式是直接在关键的方法上加synchronized。这就导致只要操作哈希表上的任意元素,都会产生加锁,也就窦娥可能会发生锁冲突。
![](https://i-blog.csdnimg.cn/blog_migrate/6ffaf98ed6bce8ad82ac99f0db9daa7b.png)
如上图所示,HashMap表中的所有元素通通用一个大锁锁上,只要一个元素进行操作,其他的就只能阻塞等待。但在实际的开发过程中,有些元素并发执行的时候,并不会产生线程安全问题,不需要锁来进行操作。这就导致该方法在实际的应用中锁冲突的概率太大,即便是不同的链表上的元素进行操作都会有锁冲突。
在进行扩容时,HashTable采用的“中奖机制”。扩容时,创建一个更大的数组空间,把旧的数组上的链表上的每个元素都搬运到新的数组上。而这个操作会在某次put的时候被触发。如果元素个数特别多,就会导致这样的搬运操作比正常的时候耗时更多,体验就是某次put操作完成比平时卡。
三、ConcurrentHashMap
ConcurrentHashMap作为更为优化的线程安全的哈希表,更为推荐使用。首先来了解一下它的加锁方式。
![](https://i-blog.csdnimg.cn/blog_migrate/4a65e975e001eccd2bd1f5a720c6d30f.png)
1、相比于HashTable,ConcurrentHashMap将一把大锁转换成了多把小锁。具体表现就是将每个链表都设置一个自己的锁,就是使用每个链表的头结点,作为锁对象。
2、ConcurrentHashMap只针对写加锁,针对读不加锁,但是使用了Volatile来保证线程的安全性。
3、ConcurrentHashMap内部充分的使用了CAS,通过这个也来进一步的削减锁操作的数目。
4、针对扩容,其采取的是“化整为零”的方法。当需要进行扩容的时候,会先创建一个新的更大的数组,同时保留旧的数组,每次进行put操作的时候,都是添加到新的数组上的,同时会搬运一小部分旧数组上的元素到新数组上。每次get操作时,都会考虑到新旧两个数组,每次remove的时候,只要把元素删了就行了。经过一段时间后,所有元素都被搬运完毕,最终释放旧数组。