集合類不安全 List不安全 單線程情況下集合類和很多其他的類都是安全的,因為同一時間只有一個線程在對他們進行修改,但是如果是多線程情況下,那麼集合類就不一定是安全的,可能會出現一條線程正在修改的同時另一條線程啟動來對這個集合進行修改,這種情況下就會導致發生併發修改異常(在jdk11的環境下多次測試 ...
集合類不安全
List不安全
單線程情況下集合類和很多其他的類都是安全的,因為同一時間只有一個線程在對他們進行修改,但是如果是多線程情況下,那麼集合類就不一定是安全的,可能會出現一條線程正在修改的同時另一條線程啟動來對這個集合進行修改,這種情況下就會導致發生併發修改異常(在jdk11的環境下多次測試該代碼發現並無問題,但是學習教程中有該異常。原因:線程數量不夠)
package org.example.unsafe;
import java.util.ArrayList;
import java.util.UUID;
public class Test1 {
public static void main(String[] args) {
ArrayList<String> sts = new ArrayList<>();
for (int i = 0; i < 10; i++) {
new Thread(() -> {
sts.add(UUID.randomUUID().toString().substring(0, 5));
System.out.println(sts);
},String.valueOf(i)).start();
}
}
}
重現該異常,通過for迴圈開更多線程
package org.example.unsafe;
import java.util.ArrayList;
import java.util.UUID;
public class Test1 {
public static void main(String[] args) {
MidiFireList midiFireList = new MidiFireList();
for (int i = 0; i < 10; i++) {
new Thread(() -> {
midiFireList.midi();
}, "A").start();
}
for (int i = 0; i < 10; i++) {
new Thread(() -> {
midiFireList.midi();
}, "B").start();
}
for (int i = 0; i < 10; i++) {
new Thread(() -> {
midiFireList.midi();
}, "C").start();
}
}
}
class MidiFireList {
ArrayList<String> sts = new ArrayList<>();
public void midi() {
sts.add(Thread.currentThread() + ":" + UUID.randomUUID().toString().substring(0, 5));
System.out.println(sts);
}
}
成功重現異常
解決List的併發修改異常
1、通過使用List的子類Vector來操作,Vector預設時線程安全的,所以不會出現以上情況,Vector時jdk1.0時期就出現的,它的add方法使用了synchronized關鍵字來保證線程安全。
class MidiFireList {
//使用了線程安全的Vector集合類
List<String> sts = new Vector<>();
public void midi() {
sts.add(Thread.currentThread() + ":" + UUID.randomUUID().toString().substring(0, 5));
System.out.println(sts);
}
}
2、通過所有集合的父類Collections類的線程安全的方法創建一個ArraryList。
class MidiFireList {
List<String> sts = Collections.synchronizedList(new ArrayList<String>());
public void midi() {
sts.add(Thread.currentThread() + ":" + UUID.randomUUID().toString().substring(0, 5));
System.out.println(sts);
}
}
3、通過JUC包下的CopyOnWriteArrayList類來創建一個ArrayList,他內部的方法通過同步代碼塊和lock鎖實現了線程安全的各種操作,缺點時少量線程操作時成本太高(CopyOnWrite寫入時複製,COW思想,是電腦程式設計領域中的一種優化策略),在寫入時複製一份,避免覆蓋導致數據問題,讀寫分離思想
CopyOnWriteArrayList和Vector的線上程安全方面的區別,為什麼要用CopyOnWriteArrayList
CopyOnWriteArrayList對比Vector,我們可以通過源碼來看
CopyOnWriteArrayList:
Vector:
jdk1.8時的CopyOnWriteArrayList:
其實在jdk11之後的區別隻在於同步代碼塊和同步方法的區別,可參考同步代碼塊和同步方法有什麼區別 • Worktile社區和瞄一眼CopyOnWriteArrayList(jdk11) - 傅曉芸 - 博客園 (cnblogs.com)這兩篇文章。
但是在jdk1.8時,CopyOnWriteArrayList的方法時單純的通過Lock鎖來實現同步的,沒有使用synchronized關鍵字,因為會影響性能。
Set不安全
Set的不安全問題與List一樣,解決方案如下
1、通過Collections的同步方法來創建一個線程安全的Set
class MidiFireList {
Set<String> set = Collections.synchronizedSet(new HashSet<>());
public void midi() {
set.add(Thread.currentThread() + ":" + UUID.randomUUID().toString().substring(0, 5));
System.out.println(set);
}
}
2、通過CopyOnWriteArraySet類來創建線程安全的Set
class MidiFireList {
Set<String> set = new CopyOnWriteArraySet<>();
public void midi() {
set.add(Thread.currentThread() + ":" + UUID.randomUUID().toString().substring(0, 5));
System.out.println(set);
}
}
HashSet的底層就是HashMap,他就不是一個新的東西
HashSet的add方法就時HashMap的put方法封裝了一下
map的key是無法重覆的,所以HashSet是無序的
Map不安全
Map解決方案
package org.example.unsafe;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
public class MapTest {
public static void main(String[] args) {
// HashMap是這樣用的嗎?不是工作中不用HashMap
// 預設等價於什麼? new HashMap<>(16,0.75);
Map<String, String> map = new ConcurrentHashMap<>();
// 載入因數、初始化容量
for (int i = 0; i < 50; i++) {
new Thread(()->{
map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0, 5));
System.out.println(map);
},String.valueOf(i)).start();
}
}
}
註意Map的併發類為ConcurrentHashMap