1.Hashtable
Hashtable所提供的方法例如put,get等都是由synchronized关键字修饰的,如源码所示:
因此调用Hashtable的对象调用这些方法时,就会自动进行加锁,在多线程的环境下虽然不会存在线程安全的问题,但是很多不需要加锁的情况,也会自动加锁,导致出现锁竞争,影响了代码的执行效率;
2.HashMap
HashMap的方法没有使用synchronized关键字修饰,如源码所示:
在多线程的应用场景下,创建HashMap时,需要程序员自己考虑线程安全问题。当出现线程安全问题时,通过加锁的方式解决;
3.ConcurrentHashMap
相比于Hashtable和HashMap,ConcurrentHashMap做了一些优化:
1.优化了锁的粒度
哈希表的冲突解决通常是采用开散列/哈希桶的方式,开散列法又叫链地址法,首先针对关键码集合用散列函数计算散列地址,具有相同地址的关键码位于同一子集,每一个子集称为一个桶,各个桶中的元素通过一个单链表连接起来,各个链表的头结点存在哈希表中。
上面提到,Hashtable加的锁是针对put,get等方法加锁,等于给this加锁,整个Hash对象就是一把锁,任何针对Hashtable的操作都会出发锁竞争;
ConcurrentHashMap是针对Hash表下的每一个链表进行加锁,会有多把锁,形成一个锁桶。当多个线程访问同一个链表,这时候才会出现锁竞争,当访问不同的链表则不会出现锁竞争。相对于Hashtable,锁的粒度变细了。如下图:
当操作不同链表中的元素,其实没必要加锁,加锁反而会降低效率。使用Hashtable就会降低效率,而ConcurrentHashMap分别针对每个链表加锁,相比于Hashtable大大减少了锁冲突,因此ConcurrentHashMap比Hashtable更高效。相对于HashMap,ConcurrentHashMap已经考虑了线程安全,直接使用比自己实现更加方便。
2.ConcurrentHashMap引入了CAS操作
虽然我们修改的是不同链表里的元素,可能也会涉及到修改同一个变量,比如说哈希表的size,引入了CAS操作修改size,就可以避免加锁操作;
3.针对扩容进行了特殊优化
普通的哈希表扩容,需要新建一个哈希表,把所有元素都复制过去。这一系列操作很可能就在一次put中就完成了,造成这一次put操作开销很大,耗时很长。
ConcurrentHashMap扩容时同样也会创建一个新的哈希表,但是不会在一次操作中搬运所有元素,而是每次操作都会搬运一部分元素,直到最终把所有的元素都搬运完成。每次只搬运一部分元素,就会降低开销和耗时,用户操作时更加流畅。
当新表和旧表同时存在的时候,插入操作会将元素插入到新表,而查询,修改,删除操作会同时查询新表和旧表。