Key 值是否可以为空
HashMap
这是 HashMap 的 hash 方法, 可以看出如果 key 值为空, hash方法的返回值即为0
Hashtable
而Hashtable源码中的put 方法中的 key 如果为空, hashCode方法会抛出空指针异常
例如:
ConcurrentHashMap
ConcurrentHashMap 的Key也不可以为空, 如下源码中的 put 方法, 如果 Key == null, 则会抛出空指针异常
Hashtable 的加锁问题
都知道Hashtable和ConcurrentHashMap 是线程安全的, 但是为什么 Hashtable 的效率低?
如下, 这是 Hashtable 源码中的一部分方法, Hashtable 的很多方法都是非常直接暴力地用 synchronized 修饰, 锁的范围太大, 很多不必要且频繁的加锁释放锁的操作大大降低了 Hashtable 性能
相比 Hashtable 和 ConcurrentHashMap
对于加锁, ConcurrentHashMap 做的比 Hashtable 更好
Hashtable 的加锁是针对整个哈希表进行加锁, 毋庸置疑, Hashtable 锁细度太粗, 一个锁就覆盖了所有哈希桶, 按理来说, 往不同的桶里放元素, 也不会让彼此收到影响
在 java 8 之前, 一个锁管理多个哈希桶, 即多个哈希桶分配一个锁。在 java 8 开始, ConcurrentHashMap 中哈希表中的每个哈希桶都有一个锁, 这每个哈希桶的锁对象就是当前链表的头结点, 锁细度更细, 能有更多线程操作一份表。
除此之外
ConcurrentHashMap 还有一些优化和特点
ConcurrentHashMap 充分使用了 CAS 的特性, 通过 CAS 维护一下变量等, 比如当前元素个数size
且针对 ConcurrentHashMap 的扩容也做出了细致的优化, 不同于 HashMap, HashMap 扩容是等某一次 put 后发现负载因子不达标后开始扩容, 新建一个表, 重新将元素哈希进新表, 这会导致这一次 put 的时间非常长。
而 ConcurrentHashMap 则是将扩容的操作和成本平摊, 如果发现了有操作 ConcurrentHashMap 的线程需要扩容, 就会创建一个新表, 然后顺便将原表中的几个元素重新哈希到新表, 在扩容没有完成前, 新表和老表都会共存, 然后接下来操作 ConcurrentHashMap 的线程中, 每次操作都会顺手将原表中若干个元素哈希到新表(搬运)。 在这期间, 每次插入操作都会放在新表中, 查询操作即同时在新表和旧表中查询。直到扩容完成, 旧表删除。