解決ABA問題

前景描述:

多個線程併發地 去get遍歷List,然後同時new 類去set,會出現線程安全問題,然後傻乎乎取stackOverfloer/githup

後臺心態不好差差點想到把for遍歷出的對象,放到queue,通過poll搞了,模擬Producer consumer的方式處理了,for處理的數量越大,,丟失的數量越多,技術不行要多讀書,見圖 

 

 實解:“那個list add ,list得換成一個線程安全的,add操作會亂的 原因就是這裏,,get是線程內操作,每個next是獨享的沒問題,,你把set全部註釋,for 1000比 併發arraylist add 最終arraylist得不到1000個 因爲它不安全,我糾結想解決的地方搞錯了,,我一直想的時next.get地方 next被“ABA”困擾了

CopyOnWriteArrayList,,Collections.synchronizedList這種都是加鎖,加鎖之後就是串行處理了

 

 

----------------------補充----------------------

CopyOnWriteArrayList的實現原理

還有處理方式就是把arraylist放到線程裏面,作爲線程的返回,最後面阻塞等待返回時addall,避免集合add的安全問題。

代碼很簡單,但是使用CopyOnWriteMap需要注意兩件事情:

  1. 減少擴容開銷。根據實際需要,初始化CopyOnWriteMap的大小,避免寫時CopyOnWriteMap擴容的開銷。

  2. 使用批量添加。因爲每次添加,容器每次都會進行復制,所以減少添加次數,可以減少容器的複製次數。如使用上面代碼裏的addBlackList方法。

CopyOnWrite的缺點 

CopyOnWrite容器有很多優點,但是同時也存在兩個問題,即內存佔用問題和數據一致性問題。所以在開發的時候需要注意一下。

  內存佔用問題。因爲CopyOnWrite的寫時複製機制,所以在進行寫操作的時候,內存裏會同時駐紮兩個對象的內存,舊的對象和新寫入的對象(注意:在複製的時候只是複製容器裏的引用,只是在寫的時候會創建新對象添加到新容器裏,而舊容器的對象還在使用,所以有兩份對象內存)。如果這些對象佔用的內存比較大,比如說200M左右,那麼再寫入100M數據進去,內存就會佔用300M,那麼這個時候很有可能造成頻繁的Yong GC和Full GC。之前我們系統中使用了一個服務由於每晚使用CopyOnWrite機制更新大對象,造成了每晚15秒的Full GC,應用響應時間也隨之變長。

  針對內存佔用問題,可以通過壓縮容器中的元素的方法來減少大對象的內存消耗,比如,如果元素全是10進制的數字,可以考慮把它壓縮成36進制或64進制。或者不使用CopyOnWrite容器,而使用其他的併發容器,如ConcurrentHashMap。

  數據一致性問題。CopyOnWrite容器只能保證數據的最終一致性,不能保證數據的實時一致性。所以如果你希望寫入的的數據,馬上能讀到,請不要使用CopyOnWrite容器。

 CopyOnWriteArrayList爲什麼併發安全且性能比Vector好

 我知道Vector是增刪改查方法都加了synchronized,保證同步,但是每個方法執行的時候都要去獲得鎖,性能就會大大下降,而CopyOnWriteArrayList 只是在增刪改上加鎖,但是讀不加鎖,在讀方面的性能就好於Vector,CopyOnWriteArrayList支持讀多寫少的併發情況。 

操作結果:

  寫操作 讀操作
  CopyOnWriteArrayList  Collections.
synchronizedList
CopyOnWriteArrayList  Collections.
synchronizedList
2 567 2 1 1
4 3088 3 2 2
8 25975 28 2 3
16 295936 44 2 6
32 3 8
64 7 21
128 9 38

        寫操作:在線程數目增加時CopyOnWriteArrayList的寫操作性能下降非常嚴重,而Collections.synchronizedList雖然有性能的降低,但下降並不明顯。

        讀操作:在多線程進行讀時,Collections.synchronizedList和CopyOnWriteArrayList均有性能的降低,但是Collections.synchronizedList的性能降低更加顯著。

4 結論

        CopyOnWriteArrayList,發生修改時候做copy,新老版本分離,保證讀的高性能,適用於以讀爲主,讀操作遠遠大於寫操作的場景中使用,比如緩存。而Collections.synchronizedList則可以用在CopyOnWriteArrayList不適用,但是有需要同步列表的地方,讀寫操作都比較均勻的地方。

---------------------------併發補充------------

併發集合

一個關於同步集合的缺點是,用集合的本身作爲鎖的對象。這意味着,在你遍歷對象的時候,這個對象的其他方法已經被鎖住,導致其他的線程必須等待。其他的線程無法操作當前這個被鎖的集合,只有當執行的線程釋放了鎖。這會導致開銷和性能較低。
這就是爲什麼jdk1.5+以後提供了併發集合的原因,因爲這樣的集合性能更高。併發集合類並放在java.util.concurrent包下,根據三種安全機制被放在三個組中。

  • 第一種爲:寫時複製集合:這種集合將數據放在一成不變的數組中;任何數據的改變,都會重新創建一個新的數組來記錄值。這種集合被設計用在,讀的操作遠遠大於寫操作的情景下。有兩個如下的實現類:CopyOnWriteArrayList 和 CopyOnWriteArraySet.
    需要注意的是,寫時複製集合不會拋出ConcurrentModificationException異常。因爲這些集合是由不可變數組支持的,Iterator遍歷值是從不可變數組中出來的,不用擔心被其他線程修改了數據。

  • 第二種爲:比對交換集合也稱之爲CAS(Compare-And-Swap)集合:這組線程安全的集合是通過CAS算法實現的。CAS的算法可以這樣理解:
    爲了執行計算和更新變量,在本地拷貝一份變量,然後不通過獲取訪問來執行計算。當準備好去更新變量的時候,他會跟他之前的開始的值進行比較,如果一樣,則更新值。
    如果不一樣,則說明應該有其他的線程已經修改了數據。在這種情況下,CAS線程可以重新執行下計算的值,更新或者放棄。使用CAS算法的集合有:ConcurrentLinkedQueue and ConcurrentSkipListMap.
    需要注意的是,CAS集合具有不連貫的iterators,這意味着自他們創建之後並不是所有的改變都是從新的數組中來。同時他也不會拋出ConcurrentModificationException異常。

  • 第三種爲:這種集合採用了特殊的對象鎖(java.util.concurrent.lock.Lock):這種機制相對於傳統的來說更爲靈活,可以如下理解:
    這種鎖和經典鎖一樣具有基本的功能,但還可以再特殊的情況下獲取:如果當前沒有被鎖、超時、線程沒有被打斷。
    不同於synchronization的代碼,當方法在執行,Lock鎖一直會被持有,直到調用unlock方法。有些實現通過這種機制把集合分爲好幾個部分來提供併發性能。比如:LinkedBlockingQueue,在隊列的開後和結尾,所以在添加和刪除的時候可以同時進行。
    其他使用了這種機制的集合有:ConcurrentHashMap 和絕多數實現了BlockingQueue的實現類
    同樣的這一類的集合也具有不連貫的iterators,也不會拋出ConcurrentModificationException異常。

我們來總結下今天我們所學到的幾個點:

  1. 大部分在java.util包下的實現類都沒有保證線程安全爲了保證性能的優越,除了Vector和Hashtable以外。
  2. 通過Collection可以創建線程安全類,但是他們的性能都比較差。
  3. 同步集合既保證線程安全也在給予不同的算法上保證了性能,他們都在java.util.concurrent包中。 

翻譯來自:
https://www.codejava.net/java-core/collections/understanding-collections-and-thread-safety-in-java

AtomicStampedReference解決ABA問題
 

Collections.synchronizedList(new ArrayList()) returns a Thread-Safe wrapper of collection where access to each method is locked on wrapper instance which means only one thread can call any methods on this wrapper. But when you want to iterate over list and perform some mutable operations then you need to synchronize iterations over this list on wrapper object, from javadoc:

It is imperative that the user manually synchronize on the returned list when iterating over it:

        List list = Collections.synchronizedList(new ArrayList());
                 ...
             synchronized (list) {
                 Iterator i = list.iterator(); // Must be in synchronized block
                 while (i.hasNext())
                     foo(i.next());
             }

Failure to follow this advice may result in non-deterministic behavior.

If it's crucial to iterate over list cuncurently then CopyOnWriteArrayList might be a way to go. It allows you to have multiple threads iterating over same list but I'm not sure if this is what you are looking for, because:

A thread-safe variant of ArrayList in which all mutative operations (add, set, and so on) are implemented by making a fresh copy of the underlying array.

Which means that all changes to the list will not be visible to threads that are already iterating over it (should be fine for you unless list is not exactly what you want). From your short description I understood that you are looking for a collection which will hold some objects and you want to have multiple threads accessing this collection and polling objects one after another for some kind of processing. If my assumption is correct then you should implement this kind of functionality using a Queue rather than List (for example a BlockingQueue).

https://stackoverflow.com/questions/23040550/how-to-access-list-of-lists-concurrently?r=SearchResult

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章