比HashTable更先进的ConcurrentHashMap及其多线程协助扩容机制
HashTable是1996年1月(我还没出生尼)Java1.0时期就开始推出的一款存储key-value类型数据的集合,彼时HashMap还没有被造出来。HashTable继承自抽象类Dictionary,它也是一个过时的遗留类,之所以没被从jdk中移除,是因为考虑到历史项目的兼容性问题。倘若移除的话,那些已经使用了Dictionary的所有项目都需要重构这部分代码,影响实在是太大了。对于我们开发者来说,我们尽量不要使用它就是了。取而代之的是更先进的Map interface和HashMap,Map interface也是后来引入的。
HashTable并发访问时效率太低了,所以才被淘汰了。因为它的get()、put()都带有synchronized修饰符,这就导致同一时间,只能有一个线程访问这个集合,性能实在令人堪忧。相比之下,jdk8版本中的ConcurrentHashMap并发访问的效率就大大提高了。它之所以高效是因为其会在存储桶级别加锁,例如a、b线程可以分别并发访问1、2两个存储桶中的key-value数据,互不影响。
下图是Doug Lea,ConcurrentHashMap的创始人,中文名为道格·利。美国国籍,现担任纽约州立大学Oswego分校教师。
多线程协助扩容机制
ConcurrentHashMap的存储桶是Node类型的对象,遇到扩容时,假如A存储桶中的节点已经被迁移到新的数组后,在老数组中的Node类会被替换为ForwardingNode这个子类,这样一来,等其它线程要访问A存储桶中的key-value时,有如下两种情形:
如果发现节点类型是ForwardingNode,就明白了当前Map正在进行扩容操作,并且当前这个存储桶中的节点已经被完全迁移到新数组了,于是它就会暂时放弃读取数据的任务,先帮着一起迁移数据到新数组,等所有key-value都迁移到新数组后,就会继续执行读取数据的任务,只不过会从新数组中读取了。
如果发现节点类型是Node类,那它就知道这个存储桶中的key-value还都没被迁移走呢,那它就可以无视正在进行的扩容操作,继续自顾自地执行访问数据的任务即可。