HashMap和HashTable、ConcurrentHashMap的区别
就线程安全来说,可分为两类
线程安全:HashTable, ConcurrentHashMap
线程不安全:HashMap
我们是如何知道这些类哪个线程安全,哪个不安全的呢?
通过查验原码可以发现:
HashMap的添加和删除,读等并没有加锁,在多线程下存在线程安全问题
而其余两个类的方法都有加锁,以put方法为例
————————————————————————————————————————————
HashTable(put 方法):
——————————————————————————————————————————
ConcurrentHashMap的putVal方法:
那么 HashTable和ConcurrentHashMap的区别又是什么呢?
HashTable和ConcurrentHashMap的区别
1.加锁方式不同:
HashTable:
根据上图HashTable的put方法可以发现,加锁是对整个方法加锁的,也就是说对this整个桶对象加锁:
这样虽然线程安全了,但带来的问题也显而易见:效率太低,因为每次添加删除都要加锁释放锁,且一次只能有一个线程添加一个元素或删除一个元素,换句话说就是锁冲突太高了。
那么为了提高效率,前面的大佬就把这个类优化了一下: ConcurrentHashMap 应运而生
ConcurrentHashMap :
我们可以观察发现:
如果我们的添加删除对的是不同的桶操作,比如A添加,F也添加,这时就算不用加锁我们的线程也是安全的。那么我们只需要保证同一个桶的操作是线程安全的就可以了,那么就没必要全局加锁,可以给每个桶进行加锁,这样就可以大大提高效率,降低了锁冲突概率。如图所示:
——————————————————————————————————————————
其次 ConcurrentHashMap的读(get)操作并没有加锁,而是采用了volatile关键字来保证内存可见性。
HashTable的get:
扩容方式
HashTable
扩容主要流程:
这种扩容方式在数据量小的情况下,并无什么缺点,但当数据量非常大的时候,就会时不时出现某一次添加操作特别消耗时间,因为那一次正好是扩容的一次,由于大量的数据拷贝,为使得put’操作非常缓慢。
这时 ConcurrentHashMap扩容方式的优势就来了
ConcurrentHashMap:
主要流程:由于其的扩容原码过长,我就文字叙述一下:
ConcurrentHashMap的扩容方式,主要是为了弥补HashTable的扩容缺点,既然一次性拷贝使得某一次put时间过长,那么我们不如分多次拷贝。也就是当扩容时,创建一块新的比原来大的空间后,我们不一次性全拷贝过去,而是拷贝一部分,把时间均摊下来。在这期间,新旧数组同时存在,如果这时执行查询操作时,我们新旧两个表一起查询,删除操作也是新旧两个表查询来删除,插入新元素操作只往新空间插入元素,直到全部拷贝完,再把旧的桶扔掉。
3.size()的更新方式
HashTable的size更新也是在锁内进行的
而ConcurrentHashMap是利用CAS的特性来更新的,简单来说CAS可以无锁化的完成值调的增加减少操作,同时也线程安全,可以大大降低锁代理的性能消耗。
结尾
感谢阅读,如有错误欢迎指出!