目录
一、Hashtable
Hashtable虽然是线程安全的,但是它只是简单地用synchronized修饰了一些关键方法,相当于是对this加锁,即对整个Hashtable对象进行加锁!
在多线程环境下,这种“无脑”的加锁方式是非常低效的:
(1) 多个线程访问同一个Hashtable对象时,无论做什么操作,都会产生锁冲突;
(2) 如果某个线程触发了扩容机制,那么就会由这个线程完成整个扩容过程,如果元素过多,效率则非常低,其它线程阻塞等待的时间也会更长。
Java官方已经不推荐使用Hashtable了~
二、ConcurrentHashMap
ConcurrentHashMap相较于Hashtable做了许多的优化,核心思路就是降低锁冲突概率~
1、锁粒度的控制
ConcurrentHashMap不是锁整个对象,而是使用多把锁,对每个链表(哈希桶)都进行加锁,只有当两个线程同时访问同一个链表时,才会产生锁竞争,因此极大地降低了锁冲突的概率。
2、读操作不加锁
ConcurrentHashMap只对写操作进行加锁,读操作没有加锁,此时会有三种情况:
(1) 两个线程同时修改一个哈希桶时才会产生锁冲突;
(2) 两个线程同时读数据,不会有锁冲突;
(3) 一个线程修改,一个线程读,也没有锁冲突。
第三种情况可能会有线程不安全问题,这和我们写的代码有关,但是ConcurrentHashMap中的读操作使用了Volatile,来保证读到的数据不是修改了一半的数据。
3、利用了CAS的特性
ConcurrentHashMap充分利用了CAS的特性,避免出现重量级锁的情况。
比如维护元素个数(size)时,就是通过CAS来更新~
4、优化扩容操作
Hashtable的扩容机制是,创建一个更大的新数组,然后由一个线程一次性把旧数组中的元素搬运到新数组。
而ConcurrentHashMap则是让新数组和旧数组同时存在一段时间,在这期间,后续每个操作ConcurrentHashMap的线程都会负责搬运旧数组的一部分元素到新数组,直到搬运完旧数组的最后一个元素时,再把旧数组删除~
搬运期间,插入元素时直接插入到新数组中;查询元素时,新数组和旧数组一起查。
三、区别:
1、HashMap线程不安全,适用于单线程环境,key值可以为null;
2、HashTable线程安全,但锁的是整个Hashtable对象,效率较低,key值不可以为null;
3、ConcurrentHashMap线程安全,锁的是每个链表的头结点,降低了锁冲突的概率;充分利用CAS机制;优化了扩容方式;key值不可以为null,适用于多线程环境。