前言
在之前HashMap的学习中,我们可以知道HashMap是线程不安全的数据结构,它存储的一般是数据的键值对(Key-Value模型),其中Key允许为null,它底层是数组+链表的实现,当单个链表的数据元素过多时,会转变为红黑树,在多线程环境下,对某个HashMap对象进行操作,是无法保证线程安全的,具体产生哪种线程不安全问题,大家可以看我之前写的这篇博客:主要的线程不安全问题有哪些?在这基础之上,我们引入另外两种线程安全的哈希表:HashTable和ConcurrentHashMap.接下来我们来讨论一下HashTable和ConcurrentHashMap使用什么方法来保证线程安全?
目录
1.HashTable保证线程安全的方式.
2.ConcurrentHashMap保证线程安全的方式.
3.总结
1.HashTable保证线程安全的方式.
● 图例
● 解释
大家可以从图例中看见HashTable是通过将整个HashTable对象进行加锁,从源码中可以看见HashTable的关键方法都加了锁,那么就意味着在多线程情况下,不管我对HashTable的某个对象进行任何增删改查操作,都可能会产生锁冲突.
注:在多线程的某些情况下,两个线程修改不同的链表的值或者往不同的链表中插入数据,是不会产生线程安全问题的,但是HashTable中是对整个HashTable对象加锁,两个线程会抢同一个HashTable对象,从而出现锁竞争,然后产生阻塞等待,这是没有必要的.
2.ConcurrentHashMap保证线程安全的方式.
● 图例
● 解释
ConcurrentHashMap对HashTable做出了优化,它增加了锁的个数,即给哈希表中的每个链表的头结点加锁,那么在多线程环境下,两个线程对ConcurrentHashMap对象的不同链表进行增删改查操作时,是不会出现锁冲突的,即锁个数增加,冲突就会相对减少.使用volatile修饰变量保证每次都是从内存读取结果,避免发生内存可见性或指令重排序的线程不安全问题.
3.总结.(重点★)
我们可以从三个方面分析HashTable和ConcurrentHashMap的区别:
① 它们加锁粒度不同,HashTable是对整个哈希表加锁,而ConcurrentHashMap是对每个链表头进行加锁,ConcurrentHashMap避免了不必要的加锁.
② 在ConcurrentHashMap中充分利用了cas(compare and swap)机制(无锁编程),像一些获取和更新元素个数等操作都是直接使用cas完成.且只针对写操作进行加锁.而HashTable没有,它是直接针对HashTable对象加锁.
③ 相比于HashTable, ConcurrentHashMap的扩容策略明显更好,当元素过多时,前者就会触发扩容操作,重新申请内存空间,一次性将旧的哈希表中的所有元素搬运到新申请的内存空间中,这样做会让成本很高,效率也会很低,而ConcurrentHashMap就不一样了,它每次只搬运一部分,两份哈希表,不断一边往新表中插入元素,另一边删除旧表的元素.
注:HashTable和ConcurrentHashMap的key都不可以为null.
还有很多不懂的地方,欢迎大家和我一起讨论学习~