HashMap为什么哪里不安全,安全管家

文章 3年前 (2021) admin
0

Q1:HashMap为什么哪里不安全?

比如一个 ArrayList 类,在添加一个元素的时候,它可能会有两步来完成:1. 在 Items[Size] 的位置存放此元素;2. 增大 Size 的值。在单线程运行的情况下,如果 Size = 0,添加一个元素后,此元素在位置 0,而且 Size=1;而如果是在多线程情况下,比如有两个线程,线程 A 先将元素存放在位置 0。但是此时 CPU 调度线程A暂停,线程 B 得到运行的机会。线程B也向此 ArrayList 添加元素,因为此时 Size 仍然等于 0 (注意哦,我们假设的是添加一个元素是要两个步骤哦,而线程A仅仅完成了步骤1),所以线程B也将元素存放在位置0。然后线程A和线程B都继续运行,都增加 Size 的值。那好,现在我们来看看 ArrayList 的情况,元素实际上只有一个,存放在位置 0,而 Size 却等于 2。这就是“线程不安全”了。

Q2:hashmap为什么线程不安全

hashmap的底层是一个Entry数组。当发生哈希冲突时,通过链表来解决HashMap,链表的头节点存储在对应的数组位置。对于链接列表,新添加的节点将从初始节点添加。javadoc中hashmap的描述如下:这个实现是不同步的。如果多个线程同时访问一个哈希映射,并且至少有一个线程在结构上修改了该映射,则它必须保持外部同步。(结构修改是指添加或删除一个或多个映射关系的任何操作;仅更改与实例中已包含的键相关联的值不是结构修改。这通常通过同步自然封装映射的对象来完成。如果没有这样的对象,您应该使用Collections.synchronizedMap方法来“包装”地图。最好在创建时这样做,以防止对地图的意外异步访问。

Q3:为什么HashMap是线程不安全的

例如,当数组列表类添加一个元素时,它可能有两个步骤要完成:1。将元素存储在项目[大小]的位置;2.增加“大小”的值。在单线程运行的情况下,如果Size=0,在添加一个元素后,元素在位置0,Size=1;例如,在多线程的情况下,有两个线程,线程a首先将元素存储在位置0。但此时,CPU调度线程A暂停,线程B获得运行的机会。线程B也向这个ArrayList添加元素,因为此时Size仍然等于0(注意,我们假设添加一个元素需要两步,但是线程A只完成了第一步),所以线程B也将元素存储在位置0。然后线程a和线程b继续运行,增加大小的值。好,现在让我们看看数组列表。实际上只有一个元素,存储在位置0,但是大小等于2。这就是“线程不安全”。

Q4:hashmap 为什么线程不安全

有2种办法让HashMap线程安全,分别如下:  方法一:通过Collections.synchronizedMap()返回一个新的Map,这个新的map就是线程安全的。 这个要求大家习惯基于接口编程,因为返回的并不是HashMap,而是一个Map的实现。  方法二:重新改写了HashMap,具体的可以查看java.util.concurrent.ConcurrentHashMap. 这个方法比方法一有了很大的改进。

Q5:hashmap为什么是线程不安全的

非线程安全一般是为了提高性能。我的英语也不是很好。jdk对HashMap的javadoc说:下面框中的jdk也提供了一个线程安全的解决方案。另一种方法是使用哈希表:

Q6:为什么hashmap线程不安全

一直以来只是知道HashMap是线程不安全的,但是到底HashMap为什么线程不安全,多线程并发的时候在什么情况下可能出现问题?HashMap底层是一个Entry数组,当发生hash冲突的时候,hashmap是采用链表的方式来解决的,在对应的数组位置存放链表的头结点。对链表而言,新加入的节点会从头结点加入。javadoc中关于hashmap的一段描述如下:此实现不是同步的。如果多个线程同时访问一个哈希映射,而其中至少一个线程从结构上修改了该映射,则它必须 保持外部同步。(结构上的修改是指添加或删除一个或多个映射关系的任何操作;仅改变与实例已经包含的键关联的值不是结构上的修改。)这一般通过对自然封装该映射的对象进行同步操作来完成。如果不存在这样的对象,则应该使用 Collections.synchronizedMap 方法来“包装”该映射。最好在创建时完成这一操作,以防止对映射进行意外的非同步访问,如下所示: Map m = Collections.synchronizedMap(new HashMap(...));1、void addEntry(int hash, K key, V value, int bucketIndex) { Entry e = table[bucketIndex]; table[bucketIndex] = new Entry(hash, key, value, e); if (size++ >= threshold) resize(2 * table.length); }在hashmap做put操作的时候会调用到以上的方法。现在假如A线程和B线程同时对同一个数组位置调用addEntry,两个线程会同时得到现在的头结点,然后A写入新的头结点之后,B也写入新的头结点,那B的写入操作就会覆盖A的写入操作造成A的写入操作丢失2、final Entry removeEntryForKey(Object key) { int hash = (key == null) ? 0 : hash(key.hashCode()); int i = indexFor(hash, table.length); Entry prev = table[i]; Entry e = prev; while (e != null) { Entry next = e.next; Object k; if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) { modCount++; size--; if (prev == e) table[i] = next; else prev.next = next; e.recordRemoval(this); return e; } prev = e; e = next; } return e; }删除键值对的代码如上:当多个线程同时操作同一个数组位置的时候,也都会先取得现在状态下该位置存储的头结点,然后各自去进行计算操作,之后再把结果写会到该数组位置去,其实写回的时候可能其他的线程已经就把这个位置给修改过了,就会覆盖其他线程的修改3、addEntry中当加入新的键值对后键值对总数量超过门限值的时候会调用一个resize操作,代码如下:void resize(int newCapacity) { Entry[] oldTable = table; int oldCapacity = oldTable.length; if (oldCapacity == MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return; } Entry[] newTable = new Entry[newCapacity]; transfer(newTable); table = newTable; threshold = (int)(newCapacity * loadFactor); }这个操作会新生成一个新的容量的数组,然后对原数组的所有键值对重新进行计算和写入新的数组,之后指向新生成的数组。当多个线程同时检测到总数量超过门限值的时候就会同时调用resize操作,各自生成新的数组并rehash后赋给该map底层的数组table,结果最终只有最后一个线程生成的新数组被赋给table变量,其他线程的均会丢失。而且当某些线程已经完成赋值而其他线程刚开始的时候,就会用已经被赋值的table作为原始数组,这样也会有问题。

版权声明:admin 发表于 2021年10月24日 上午9:59。
转载请注明:HashMap为什么哪里不安全,安全管家 | 热豆腐网址之家

相关文章