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、加鎖等。