Java多線程高并發(fā)中解決ArrayList與HashSet和HashMap不安全的方案
1.ArrayList的線程不安全解決方案
將main方法的第一行注釋打開,多執(zhí)行幾次,會(huì)看到如下圖這樣的異常信息:👇👇👇
這是一個(gè) 并發(fā)修改 異常,首先ArrayList肯定是線程不安全的,產(chǎn)生這個(gè)異常的原因就是可能第一個(gè)線程剛進(jìn)入 ArrayList 集合中要進(jìn)行 add 操作時(shí),另外一個(gè)線程此時(shí)也進(jìn)來進(jìn)行 add 操作,而第三個(gè)線程又進(jìn)來進(jìn)行 get 操作,導(dǎo)致讀寫沒辦法進(jìn)行同步了,最終打印結(jié)果的時(shí)候就炸了。
解決方案看代碼中的剩下幾行注釋。
package test.notsafe; import java.util.*; import java.util.concurrent.CopyOnWriteArrayList; /** * 演示ArrayList的線程不安全問題及解決方案 */ public class ThreadDemo2 { public static void main(String[] args) { //List<String> list = new ArrayList<>(); //解決方法1:使用Vector //List<String> list = new Vector<>(); //解決方法2:Collections //List<String> list = Collections.synchronizedList(new ArrayList<>()); //解決方法3:CopyOnWriteArrayList List<String> list = new CopyOnWriteArrayList<>(); for (int i = 0; i < 10; i++) { new Thread(() -> { list.add(UUID.randomUUID().toString().substring(0,8)); System.out.println(list); },String.valueOf(i)).start(); } } }
關(guān)于 CopyOnWriteArrayList 解決線程不安全問題的簡單解釋:就看源碼中的 add(E e) 這個(gè)方法:
public boolean add(E e) { final ReentrantLock lock = this.lock; lock.lock(); try { Object[] elements = getArray(); int len = elements.length; Object[] newElements = Arrays.copyOf(elements, len + 1); newElements[len] = e; setArray(newElements); return true; } finally { lock.unlock(); } }
這個(gè) CopyOnWriteArrayList 在進(jìn)行 add 添加操作之前,先進(jìn)行 lock 上鎖,然后通過 getArray() 獲取到原 ArrayList 集合容器,之后調(diào)用 Arrays.copyOf 方法將原容器拷貝出一個(gè)新容器,因?yàn)橐砑樱ㄩL度自然也要 +1),之后向這個(gè)新容器中添加元素,添加完成之后,調(diào)用 setArray 方法將原容器的引用指向了這個(gè)新的容器。 那么這樣做的好處就是:添加元素在新容器中,原容器該是啥樣還是啥樣,其他線程要get讀取元素就還從原容器中讀(即多個(gè)線程可以進(jìn)行并發(fā)讀);而其他線程要 add 添加,要等待其他線程完成之后,將原容器的引用指向新容器就可以了。
CopyOnWrite 容器在面對讀和寫的時(shí)候是兩個(gè)不同的容器,也是用到了讀寫分離的思想。
2.HashSet的線程不安全解決方案
這里如果是 new HashSet 了話,仍然可能出現(xiàn)向上面 ArrayList 一樣的 并發(fā)修改異常。解決方案看代碼中的注釋。
package test.notsafe; import java.util.Collections; import java.util.HashSet; import java.util.Set; import java.util.UUID; import java.util.concurrent.CopyOnWriteArraySet; /** * 演示HashSet的線程不安全問題及解決方案 */ public class ThreadDemo3 { public static void main(String[] args) { //Set<String> set = new HashSet<>(); //解決方法1:Collections //Set<String> set = Collections.synchronizedSet(new HashSet<>()); //解決方法2:CopyOnWriteArraySet Set<String> set = new CopyOnWriteArraySet<>(); for (int i = 0; i < 20; i++) { new Thread(() -> { set.add(UUID.randomUUID().toString().substring(0,8)); System.out.println(set); },String.valueOf(i)).start(); } } }
3.HashMap的線程不安全解決方案
package test.notsafe; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; /** * 演示HashMap的線程不安全問題及解決方案 */ public class ThreadDemo4 { public static void main(String[] args) { //Map<String,Object> map = new HashMap<>(); //解決方法1:Collections //Map<String,Object> map = Collections.synchronizedMap(new HashMap<>()); //解決方法2:ConcurrentHashMap Map<String,Object> map = new ConcurrentHashMap<>(); for (int i = 0; i < 10; i++) { String key = String.valueOf(i); new Thread(() -> { map.put(key,UUID.randomUUID().toString().substring(0,8)); System.out.println(map); },String.valueOf(i)).start(); } } }
到此這篇關(guān)于Java多線程高并發(fā)中解決ArrayList與HashSet和HashMap不安全的方案的文章就介紹到這了,更多相關(guān)Java 多線程高并發(fā)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring gateway + Oauth2實(shí)現(xiàn)單點(diǎn)登錄及詳細(xì)配置
gateway是基于 WebFlux的響應(yīng)式編程框架,所以在使用securityConfig時(shí)采用的注解是@EnableWebFluxSecurity,接下來通過本文給大家介紹Spring gateway + Oauth2實(shí)現(xiàn)單點(diǎn)登錄及詳細(xì)配置,感興趣的朋友一起看看吧2021-09-09JavaWeb實(shí)現(xiàn)mysql數(shù)據(jù)庫數(shù)據(jù)的添加和刪除
這篇文章主要介紹了如何利用JavaWeb實(shí)現(xiàn)mysql數(shù)據(jù)庫數(shù)據(jù)的添加和刪除功能,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解一下2022-03-03Maven實(shí)戰(zhàn)之搭建Maven私服和鏡像的方法(圖文)
本篇文章主要介紹了搭建Maven私服和鏡像的方法(圖文),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-12-12Javaweb動(dòng)態(tài)開發(fā)最重要的Servlet詳解
動(dòng)態(tài)web的核心是Servlet,由tomcat解析并執(zhí)行,本質(zhì)是Java中的一個(gè)類(面向?qū)ο螅┻@個(gè)類的功能十分強(qiáng)大幾乎可以完成全部功能,在Java規(guī)范中只有Servlet實(shí)現(xiàn)類實(shí)例化的對象才能被瀏覽器訪問,所以掌握Servlet具有重要意義2022-08-08Java數(shù)據(jù)類型Integer與int的區(qū)別詳細(xì)解析
這篇文章主要介紹了Java數(shù)據(jù)類型Integer與int的區(qū)別詳細(xì)解析,Ingeter是int的包裝類,int的初值為0,Ingeter的初值為null,int和integer(無論new否)比,都為true,因?yàn)闀?huì)把Integer自動(dòng)拆箱為int再去比,需要的朋友可以參考下2023-12-12