点击Java编程技术天堂,轻松关注!及时获取有趣且信息量大的技术文章。
做一个积极向上的人。
编码,修复bug,提升自己。
我有一个乐园,它面向编程,开满了春花!
0.HashMap简单说几句
当我们研究HashMap时,我们都知道HashMap是非线程安全的,我们也知道HashTable是线程安全的,因为其中的方法是同步的。
但是为什么HashMap是非线程安全的呢?仅仅是因为内部方法没有用同步的关键字修饰吗?本文主要分析原因。
我们知道HashMap的底层是一个Entry数组。当发生哈希冲突时,通过链表来解决HashMap,链表的头节点存储在对应的数组位置。对于链表,新添加的节点将从第一个节点开始添加。
HashMap为什么线程不安全,在什么情况下多个线程并发时可能会出现问题?
Javadoc中关于hashmap的一段描述如下:
这个实现是不同步的。如果多个线程同时访问一个哈希映射,并且其中至少有一个线程在结构上修改了该映射,那么它必须保持外部同步。(结构修改是指添加或删除一个或多个映射关系的任何操作;仅更改与实例中已包含的键相关联的值不是结构修改。)这通常通过同步自然封装映射的对象来完成。如果使用Collections.synchronizedMap方法来“包装”地图。最好在创建时这样做,以防止对映射的意外异步访问,如下所示:
map map=collections . synchronized map(新HashMap());
1.插入时的哈希映射
上面的addEntry方法将在Hashmap进行put操作时调用。
现在,如果线程A和线程B同时对同一个数组位置调用addEntry,两个线程会同时得到当前的头节点,然后A写入新的头节点后,B也写入新的头节点,那么B的写操作会覆盖A的写操作,导致A的写操作丢失。
2.当Hashmap扩展其容量时
添加新的键值对后,当键值对的总数超过阈值时,AddEntry将调用调整大小操作。代码如下:
此操作将生成新容量的新数组,然后重新计算原始数组的所有键值对并写入新数组,然后指向新生成的数组。
HashMap有一个扩展操作,它将生成一个新容量的新数组,然后重新计算原始数组的所有键值对并将其写入新数组,然后指向新生成的数组。
然后,问题来了。当多个线程同时进入并检测到总数超过阈值时,将同时调用调整大小操作。每个线程将生成一个新数组,并在重新散列后将其分配给地图底部的数组。因此,只有最后一个线程生成的新数组将被分配到映射的底部,所有其他线程都将丢失。而且,当一些线程已经完成赋值,而其他线程刚刚开始时,它们会使用分配的表作为原始数组,这也会导致问题。
其他地方可能存在很多线程安全问题,我就不一一列举了。简而言之,HashMap是非线程安全的,当出现并发问题时,建议使用ConcrrentHashMap。