HashMap线程不安全体现在哪里?假如你到现在还不清楚赶忙看下去,明明白白补一补~。

在Java中,HashMap是一种常用的数据结构,它以键值对的形式存储和管理数据。但是,因为HashMap在多线程环境下存在线程安全问题,因而在运用时需求分外当心。

简略来说:在hashMap1.7 中扩容的时分,因为采用的是头插法,所以会或许会有循环链表产生,导致数据有问题,在 1.8 版别已修复,改为了尾插法;
在任意版别的 hashMap 中,假如在刺进数据时多个线程命中了同一个槽,或许会有数据掩盖的状况产生,导致线程不安全。

HashMap的线程不安全主要体现在以下两个方面:

1. 并发修正导致数据纷歧致

HashMap的数据结构是基于数组和链表完成的。在进行刺进或删去操作时,假如不同线程一起修正同一个方位的元素,就会导致数据纷歧致的状况。具体来说,当两个线程一起进行刺进操作时,假定它们都要刺进到同一个数组方位,而且该方位没有元素,那么它们都会认为该方位能够刺进元素,终究就会导致其中一个线程的元素被掩盖掉。此外,在进行删去操作时,假如两个线程一起删去同一个元素,也会导致数据纷歧致的状况。

以下是一个示例代码,展示了两个线程对HashMap进行并发修正的状况:

import java.util.HashMap;
public class HashMapThreadUnsafeExample {
    public static void main(String[] args) throws InterruptedException {
        final HashMap<String, Integer> map = new HashMap<>();
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                map.put("key" + i, i);
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                map.put("key" + i, i * 2);
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("map size: " + map.size());
    }
}

上述示例代码中,t1线程和t2线程都向HashMap中刺进数据,因为它们在进行刺进操作时修正的是同一个方位的元素,因而终究导致了部分数据纷歧致的状况。例如,当t1线程刺进了(key1, 1)以后,t2线程又刺进了(key1, 2),这就导致了(key1, 1)被掩盖掉,终究HashMap的大小只要10000而不是20000。

2. 并发扩容导致死循环或数据丢掉

当HashMap的元素数量到达必定阈值时,它会触发扩容操作,即从头分配更大的数组并将本来的元素从头映射到新的数组上。但是,在进行扩容操作时,假如不加锁或者加锁不正确,就或许导致死循环或者数据丢掉的状况。具体来说,当两个线程一起进行扩容操作时,它们或许会一起将某个元素映射到新的数组上,然后导致该元素被掩盖掉。此外,在进行扩容操作时,假如线程不安全地修正了next指针,就或许会导致死循环的状况。

以下是一个示例代码,展示了两个线程对HashMap进行并发扩容的状况:

import java.util.HashMap;
public class HashMapThreadUnsafeExample {
    public static void main(String[] args) throws InterruptedException {
        final HashMap<String, Integer> map = new HashMap<>(2, 0.75f);
        map.put("key1", 1);
        map.put("key2", 2);
        map.put("key3", 3);
        Thread t1 = new Thread(() -> {
            for (int i = 4; i < 10000; i++) {
                map.put("key" + i, i);
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 4; i < 10000; i++) {
                map.put("key" + i, i * 2);
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("map size: " + map.size());
    }
}

上述示例代码中,t1线程和t2线程都向HashMap中刺进数据,而且HashMap被初始化为大小为2,负载因子为0.75,这就意味着HashMap在元素数量到达3时就会进行扩容操作。因为t1和t2线程一起进行扩容操作,它们有或许都将某个元素映射到新的数组上,导致该元素被掩盖掉。此外,在进行扩容操作时,假如线程不安全地修正了next指针,就或许会导致死循环的状况。

除了并发修正和并发扩容外,还有以下状况或许导致HashMap不安全:

3. 非线程安全的迭代器

当运用非线程安全的迭代器遍历HashMap时,假如在遍历的过程中其他线程修正了HashMap的结构,就或许抛出ConcurrentModificationException异常。

以下是一个示例代码,展示了如何通过多线程遍历HashMap以及导致线程不安全的状况:

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
public class HashMapThreadUnsafeExample {
    public static void main(String[] args) throws InterruptedException {
        final Map<String, Integer> map = new HashMap<>();
        for (int i = 0; i < 10000; i++) {
            map.put("key" + i, i);
        }
        Thread t1 = new Thread(() -> {
            Iterator<Map.Entry<String, Integer>> iterator = map.entrySet().iterator();
            while (iterator.hasNext()) {
                System.out.println(iterator.next().getValue());
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 10000; i < 20000; i++) {
                map.put("key" + i, i);
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
    }
}

上述示例代码中,t1线程遍历了HashMap中的元素,但并没有对其进行加锁维护。一起,在t1线程遍历的过程中,t2线程又进行了另外一部分元素的刺进操作,这就导致了HashMap结构的不稳定性,终究或许会抛出ConcurrentModificationException异常。

4. 非线程安全的比较器

当运用非线程安全的比较器来界说HashMap的排序规矩时,就或许导致在并发环境下呈现数据纷歧致性的状况。

以下是一个示例代码,展示了如何通过多线程修正HashMap中元素次序以及导致线程不安全的状况:

import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
public class HashMapThreadUnsafeExample {
    public static void main(String[] args) throws InterruptedException {
        final Map<String, Integer> map = new HashMap<>();
        map.put("key1", 1);
        map.put("key2", 2);
        map.put("key3", 3);
        Comparator<String> comparator = (s1, s2) -> {
            int i1 = Integer.parseInt(s1.substring(3));
            int i2 = Integer.parseInt(s2.substring(3));
            return Integer.compare(i1, i2);
        };
        Thread t1 = new Thread(() -> {
            for (int i = 4; i < 10000; i++) {
                map.put("key" + i, i);
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 4; i < 10000; i++) {
                map.put("key" + i, i * 2);
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("map: " + map);
    }
}

上述示例代码中,HashMap的排序规矩运用了一个基于字符串处理的比较器来界说。当t1线程和t2线程一起进行刺进操作时,因为它们在不同的元素上执行修正操作,因而并不会呈现ConcurrentModificationException异常。但是,因为比较器不是线程安全的,当t1和t2线程一起进行对相同的元素值进行赋值操作时,就或许导致HashMap结构的不稳定性。例如,当t1线程将”key5″的值修正为5时,t2线程或许只修正到”value”字段的一部分,因而终究HashMap中的值或许呈现混乱的状况。

写到这儿我想告知我们:HashMap在多线程环境下存在线程安全问题,具体表现为并发修正导致数据纷歧致和并发扩容导致死循环或数据丢掉。因而,在运用HashMap时需求采纳相应的线程安全措施,例如运用ConcurrentHashMap、加锁等。