Java如何解決ArrayList的并發(fā)問(wèn)題
ArrayList是java.util包中的一個(gè)類,它不是線程安全的。
如果多個(gè)線程同時(shí)對(duì)同一個(gè)ArrayList進(jìn)行操作,可能會(huì)導(dǎo)致并發(fā)問(wèn)題,如數(shù)據(jù)不一致或ConcurrentModificationException異常。
1. 場(chǎng)景復(fù)現(xiàn)
1.1 數(shù)據(jù)不一致問(wèn)題示例代碼
import java.util.ArrayList;
import java.util.List;
public class ArrayListConcurrencyExample {
public static void main(String[] args) {
List<Integer> arrayList = new ArrayList<>();
// 創(chuàng)建并啟動(dòng)多個(gè)線程,同時(shí)向ArrayList添加元素
Runnable addTask = () -> {
for (int i = 0; i < 1000; i++) {
arrayList.add(i);
}
};
Thread thread1 = new Thread(addTask);
Thread thread2 = new Thread(addTask);
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 輸出ArrayList的大小,不一定是預(yù)期的 2000
System.out.println("Size of arrayList: " + arrayList.size());
}
}
1.2 ConcurrentModificationException 問(wèn)題示例代碼
ConcurrentModificationException通常會(huì)在迭代ArrayList(或其他集合)的同時(shí)對(duì)其進(jìn)行結(jié)構(gòu)性修改時(shí)拋出。
import java.util.ArrayList;
import java.util.List;
import java.util.Iterator;
public class ConcurrentModificationExample {
public static void main(String[] args) {
List<String> arrayList = new ArrayList<>();
arrayList.add("Item1");
arrayList.add("Item2");
arrayList.add("Item3");
// 獲取迭代器
Iterator<String> iterator = arrayList.iterator();
// 開(kāi)始迭代
while (iterator.hasNext()) {
String item = iterator.next();
System.out.println(item);
// 在迭代過(guò)程中嘗試修改ArrayList的結(jié)構(gòu),會(huì)引發(fā)ConcurrentModificationException
if (item.equals("Item2")) {
arrayList.remove(item);
}
}
}
}
當(dāng)處理ArrayList的并發(fā)問(wèn)題時(shí),不同的方法有不同的細(xì)節(jié)和適用場(chǎng)景。以下是對(duì)每種方法的詳細(xì)解釋:
2. 解決并發(fā)的三種方法
2.1 使用 Collections.synchronizedList
使用 Collections.synchronizedList 創(chuàng)建線程安全的ArrayList這是一種簡(jiǎn)單的方式來(lái)使ArrayList線程安全。
它實(shí)際上是包裝了一個(gè)原始的ArrayList,并在每個(gè)方法上添加synchronized關(guān)鍵字來(lái)確保每個(gè)方法在同一時(shí)間只能由一個(gè)線程訪問(wèn)。
List<String> synchronizedList = Collections.synchronizedList(new ArrayList<>());
這種方法適用于那些多數(shù)情況下是讀操作,但偶爾需要寫(xiě)操作的情況。
請(qǐng)注意,盡管每個(gè)方法都是線程安全的,但多個(gè)操作之間沒(méi)有原子性保證,因此還需要其他方式來(lái)確保多個(gè)操作的一致性。
例如下面的代碼就會(huì)出現(xiàn)并發(fā)問(wèn)題:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class SynchronizedListExample {
public static void main(String[] args) {
List<Integer> synchronizedList = Collections.synchronizedList(new ArrayList<>());
Runnable addAndRemoveTask = () -> {
for (int i = 0; i < 1000; i++) {
synchronizedList.add(i);
synchronizedList.remove(synchronizedList.size() - 1);
}
};
Thread thread1 = new Thread(addAndRemoveTask);
Thread thread2 = new Thread(addAndRemoveTask);
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Size of synchronizedList: " + synchronizedList.size());
}
}在這個(gè)示例中,兩個(gè)線程同時(shí)執(zhí)行add和remove操作。雖然每個(gè)操作本身是線程安全的,但它們的組合會(huì)導(dǎo)致競(jìng)態(tài)條件,多次運(yùn)行后,會(huì)出現(xiàn)下面的情況:
最終列表的大小可能不是預(yù)期的 2000。
由于兩個(gè)線程同時(shí)進(jìn)行remove操作,可能導(dǎo)致其中一個(gè)線程試圖刪除一個(gè)元素,但在另一個(gè)線程之前已經(jīng)刪除了,導(dǎo)致IndexOutOfBoundsException異常或其他不一致的結(jié)果
Exception in thread "Thread-0" java.lang.IndexOutOfBoundsException: Index 1 out of bounds for length 1
at java.base/jdk.internal.util.Preconditions.outOfBounds(Preconditions.java:64)
at java.base/jdk.internal.util.Preconditions.outOfBoundsCheckIndex(Preconditions.java:70)
at java.base/jdk.internal.util.Preconditions.checkIndex(Preconditions.java:248)
at java.base/java.util.Objects.checkIndex(Objects.java:372)
at java.base/java.util.ArrayList.remove(ArrayList.java:536)
at java.base/java.util.Collections$SynchronizedList.remove(Collections.java:2435)
at com.test.testlist.SynchronizedListExample.lambda$main$0(SynchronizedListExample.java:14)
at java.base/java.lang.Thread.run(Thread.java:834)
Size of synchronizedList: 1
這突顯了Collections.synchronizedList在某些情況下可能無(wú)法提供足夠的并發(fā)保護(hù),因此需要額外的同步措施或選擇更適合并發(fā)操作的數(shù)據(jù)結(jié)構(gòu)。
2.2 使用 CopyOnWriteArrayList(推薦使用)
CopyOnWriteArrayList是一種并發(fā)集合,它通過(guò)在寫(xiě)操作時(shí)創(chuàng)建一個(gè)新的副本來(lái)解決并發(fā)問(wèn)題。
這意味著讀操作不會(huì)受到寫(xiě)操作的影響,而且不會(huì)拋出ConcurrentModificationException異常。
List<String> list = new CopyOnWriteArrayList<>();
這種方法適用于讀操作頻繁,寫(xiě)操作較少的情況,因?yàn)閷?xiě)操作會(huì)比較昂貴。但它非常適用于多線程下的讀操作,因?yàn)樗恍枰~外的同步。
2.3 使用顯式的同步控制
這種方法需要在需要修改ArrayList的地方使用synchronized塊或鎖來(lái)確保線程安全。
這是一種更精細(xì)的控制方法,適用于需要更多控制和協(xié)同操作的場(chǎng)景。
List<String> list = new ArrayList<>();
// 在需要修改list的地方加鎖
synchronized (list) {
list.add("item");
}這種方式要求手動(dòng)管理鎖,通過(guò)加鎖確保在修改ArrayList時(shí)進(jìn)行同步,以防止多個(gè)線程同時(shí)訪問(wèn)它。
總結(jié)
- 一般在日常編碼中,直接使用
CopyOnWriteArrayList就能滿足很多場(chǎng)景; - 但是由于每次進(jìn)行寫(xiě)操作時(shí),都需要復(fù)制整個(gè)列表,這會(huì)導(dǎo)致寫(xiě)操作的性能較低,尤其在列表很大時(shí)。因此,
CopyOnWriteArrayList適用于讀操作頻繁、寫(xiě)操作較少的場(chǎng)景。 - 使用
CopyOnWriteArrayList時(shí)候,應(yīng)該避免在迭代過(guò)程中修改列表; CopyOnWriteArrayList的迭代器具有弱一致性,在迭代過(guò)程中,迭代器可能無(wú)法反映出最新的修改,可能會(huì)遺漏或重復(fù)元素。如果非要強(qiáng)一致性,那就需要全局鎖或分布式鎖來(lái)處理了。- 大多數(shù)場(chǎng)景中,更多的還是讀多寫(xiě)少;
- 所以一般解決并發(fā)的方法,其實(shí)就是讓并發(fā)寫(xiě)的操作,變成串行的;如果非要保證最終的強(qiáng)一致性,那肯定最終還是串行化處理,非常影響性能。
- 如果是分布式系統(tǒng)的話,那肯定就要使用分布式鎖來(lái)處理了。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
SpringBoot項(xiàng)目構(gòu)建Maven標(biāo)簽及屬性用法詳解
在?Spring?Boot?項(xiàng)目中,Maven?是最常用的構(gòu)建工具之一,本文將詳細(xì)介紹?Maven?依賴管理中的主要標(biāo)簽及其使用方法,幫助開(kāi)發(fā)者更好地理解和使用?Maven?構(gòu)建工具,感興趣的朋友跟隨小編一起看看吧2024-08-08
解決springboot項(xiàng)目找不到resources目錄下的資源問(wèn)題
這篇文章主要介紹了解決springboot項(xiàng)目找不到resources目錄下的資源問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08
解決Request.getParameter獲取不到特殊字符bug問(wèn)題
這篇文章主要介紹了解決Request.getParameter獲取不到特殊字符bug問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-07-07
Java畢業(yè)設(shè)計(jì)實(shí)戰(zhàn)之在線高中考試系統(tǒng)的實(shí)現(xiàn)
這是一個(gè)使用了java+SSM+Jsp+Mysql+Maven開(kāi)發(fā)的在線高中考試系統(tǒng),是一個(gè)畢業(yè)設(shè)計(jì)的實(shí)戰(zhàn)練習(xí),具有考試系統(tǒng)該有的所有功能,感興趣的朋友快來(lái)看看吧2022-02-02
Postman實(shí)現(xiàn)傳List<String>集合
這篇文章主要介紹了Postman實(shí)現(xiàn)傳List<String>集合方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08
QTabWidget標(biāo)簽實(shí)現(xiàn)雙擊關(guān)閉的方法(推薦)
這篇文章主要介紹了QTabWidget標(biāo)簽實(shí)現(xiàn)雙擊關(guān)閉的方法(推薦)的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-06-06

