背景
存入redis的值,可能會出現(xiàn)錯誤的情況。如果出現(xiàn)錯誤,接口將會報錯。
多個方法一起修改一個公共變量的值,造成數(shù)據(jù)混亂,導(dǎo)致存入redis中的key值錯誤
還有每次登陸都會重現(xiàn)創(chuàng)建一個對象,放到公共變量中,遇到并發(fā),對象會被大量地創(chuàng)建,
上一個對象會失去引用,等待垃圾回收器進行回收,導(dǎo)致CPU飆升。
上邊公共變量的字符串拼接出現(xiàn)問題,導(dǎo)致下邊這張圖中的域名中的字符串出現(xiàn)問題。
由上圖可知:
**1、使用了線程不安全的ArrayList作為公共變量
2、每次給Arraylist重新賦值的時候都創(chuàng)建了一個新的對象,堆積了大量要回收的舊對象,導(dǎo)致CPU飆升**
(GC會消耗大量CPU和內(nèi)存來實現(xiàn)垃圾回收)
思路&方案
復(fù)現(xiàn)問題:
測試類:
public class ThreadTest {
//新建一個list作為成員變量
List<String> testList ;
public void updateTestList(){
testList = new ArrayList<>();
testList.add("a01+");
testList.add("a02+");
testList.add("a03+");
testList.add("a04+");
//打印一下看看有什么
System.out.println("updateTestList"+testList);
}
public void updateTestList2(){
testList = new ArrayList<>();
testList.add("b01+");
testList.add("b02+");
testList.add("b03+");
testList.add("b04+");
//看一下list里有什么
System.out.println("updateTestList2"+testList);
}
}
客戶端:
public class Main {
public static void main(String[] args) {
ThreadTest threadTest = new ThreadTest();
//開一個多線程測試一下
for (int i = 0; i < 100; i++) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
threadTest.updateTestList();
threadTest.updateTestList2();
}
});
thread.start();
}
}
}
正常結(jié)果只會出現(xiàn)下面兩種情況
updateTestList[a01+, a02+, a03+, a04+]
updateTestList2[b01+, b02+, b03+, b04+]
實際上:
注重變量的作用域和生命周期,還要考慮并發(fā)量高的時候考慮線程安全,并發(fā)的時候還要將對象進行置空。
第一個問題解決方案:
1、在方法之前加 synchronized 關(guān)鍵字。
2、使用ThreadLocal變量。
public class ThreadTest2 {
ThreadLocal<List<String>> testList = ThreadLocal.withInitial(()->new ArrayList<>());
public void updateTestList(){
testList.get().removeAll(testList.get());
testList.get().add("a01+");
testList.get().add("a02+");
testList.get().add("a03+");
testList.get().add("a04+");
//打印一下看看有什么
System.out.println("updateTestList"+testList.get());
}
public void updateTestList2(){
testList.get().removeAll(testList.get());
testList.get().add("b01+");
testList.get().add("b02+");
testList.get().add("b03+");
testList.get().add("b04+");
//看一下list里有什么
System.out.println("updateTestList2"+testList.get());
}
}
結(jié)果:
第二個問題(對象重復(fù)創(chuàng)建導(dǎo)致CPU和內(nèi)存飆升)解決方案:
1、使用List的RemoveAll方法將對象進行清除。
現(xiàn)狀:
這樣就不會持續(xù)開辟內(nèi)存空間。
總結(jié)
考慮成本,凡事都要考慮成本。
我們要有無限思維,當(dāng)只有一個對象的時候我們寫的代碼不會出現(xiàn)上述問題,但是對象一多就會出現(xiàn)數(shù)據(jù)錯亂的問題,內(nèi)存飆升的問題,我們的系統(tǒng)不會只有一個用戶,所以無限思維是我們必須要考慮的一件事情,考慮并發(fā),考慮將來。而不是只顧眼前。
并發(fā)如何解決?
并發(fā)問題是在多線程或多進程環(huán)境中經(jīng)常出現(xiàn)的挑戰(zhàn),可能導(dǎo)致數(shù)據(jù)不一致、死鎖等問題。解決并發(fā)問題需要考慮各種技術(shù)和策略,下面是一些常見的方法:
鎖機制: 使用鎖是解決并發(fā)問題的一種常見方法。通過在關(guān)鍵代碼段周圍加鎖,只允許一個線程或進程訪問臨界資源,從而防止多個線程同時修改相同數(shù)據(jù)。然而,鎖可能導(dǎo)致死鎖和性能問題,因此需要小心設(shè)計和管理。
同步機制: 同步方法如synchronized塊或方法可以確保在同一時間只有一個線程可以訪問共享資源。這有助于避免數(shù)據(jù)競爭和不一致問題。
并發(fā)容器: Java提供了許多并發(fā)容器,如ConcurrentHashMap、ConcurrentLinkedQueue等,這些容器在多線程環(huán)境中提供了更好的性能和線程安全性。
原子操作: 原子操作是不可分割的操作,可以確保多線程環(huán)境中的數(shù)據(jù)一致性。Java提供了一些原子類,如AtomicInteger、AtomicReference等,用于執(zhí)行原子操作。
線程池: 使用線程池可以有效地管理并發(fā)任務(wù)。線程池可以控制線程的數(shù)量,從而減少線程創(chuàng)建和銷毀的開銷,提高性能和資源利用率。
避免共享狀態(tài): 盡量避免共享狀態(tài),通過將狀態(tài)封裝在對象中,每個線程操作自己的對象實例,從而避免競爭和并發(fā)問題。
使用不可變對象: 不可變對象在多線程環(huán)境中是線程安全的,因為它們不會改變。使用不可變對象可以避免并發(fā)問題。
內(nèi)存模型和可見性: 了解Java內(nèi)存模型和可見性原則,確保一個線程對共享變量的修改對其他線程可見。
死鎖避免和解決: 死鎖是一種并發(fā)問題,當(dāng)多個線程互相等待對方釋放資源時發(fā)生。避免死鎖可以通過破壞四個必要條件之一來實現(xiàn):互斥、持有并等待、不可搶占、循環(huán)等待。
并發(fā)工具: Java提供了許多并發(fā)工具,如CountDownLatch、CyclicBarrier、Semaphore等,用于協(xié)調(diào)多個線程的執(zhí)行和等待。文章來源:http://www.zghlxwxcb.cn/news/detail-783528.html
總之,解決并發(fā)問題需要綜合考慮設(shè)計、同步、數(shù)據(jù)管理和線程控制等方面。選擇適當(dāng)?shù)牟呗院图夹g(shù)取決于問題的復(fù)雜性和性能要求。同時,良好的并發(fā)編程實踐也需要深入了解多線程和并發(fā)編程的原則。文章來源地址http://www.zghlxwxcb.cn/news/detail-783528.html
到了這里,關(guān)于案例15-ArrayList線程不安全,共用全局變量導(dǎo)致數(shù)據(jù)錯亂問題,占用內(nèi)存情況的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!