国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

帶你了解集合世界的fail-fast機制 和 CopyOnWriteArrayList 源碼詳解

young.li / 2305人閱讀

摘要:體現的就是適配器模式。數組對象集合世界中的機制機制集合世界中比較常見的錯誤檢測機制,防止在對集合進行遍歷過程當中,出現意料之外的修改,會通過異常暴力的反應出來。而在增強循環中,集合遍歷是通過進行的。

前言

學習情況記錄

時間:week 2

SMART子目標 :Java 容器

記錄在學習Java容器 知識點中,關于List的重點知識點。

知識點概覽:

容器中的設計模式

從Arrays.asList() 看集合與數組的關系

集合世界中的 fail-fast 機制

什么是 fail-fast 機制

ArrayList.sublist() 有什么坑?

foreach 循環里為什么不能進行元素的 remove/add 操作?

集合世界中的 fail-safe 機制

copy-on-write 機制

CopyOnWriteArrayList

關鍵知識點

讀寫操作

遍歷 - COWIterator

缺點 和 使用時需要注意的點

提問

容器中的設計模式 1.迭代器模式

迭代器模式指的就是 提供一種方法順序訪問一個聚合對象中各個元素, 而又無須暴露該對象的內部表示,為遍歷不同的聚合結構提供一個統一的接口。

Collection 繼承了 Iterable 接口,其中的 iterator() 方法能夠產生一個 Iterator 對象,通過這個對象就可以迭代遍歷 Collection 中的元素。

從 JDK 1.5 之后可以使用foreach 方法來遍歷實現了 Iterable 接口的聚合對象

2. 適配器模式

適配器模式使得原本由于接口不兼容而不能一起工作的那些類可以一起工作。

Arrays.asList(T... a)體現的就是適配器模式。

拿生活中的例子作比方:我很早以前用的是3.5mm耳機孔的耳機,后面我換手機了,只能用type-c的耳機,通過type-c轉接頭我以前的耳機還是能用,這里面就用了適配器模式;在上面的例子中,入參數組就是3. 5mm耳機,Arrays.asList()這整個方法就是起到適配器type-c轉接頭的作用,List就是支持我type-c口的耳機

從 Arrays.asList() 看集合與數組的關系(內含坑)

數組與集合都是用來存儲對象的容器,前者性質單一、簡單易用;后者類型安全,功能強大,而兩者之間必然有相互轉換的方式。

由于兩者的特性存在很大的差別,所以在轉換過程當中,如果不去詳細了解背后的轉換方式,很容易產生意料之外的問題。

在數組轉集合的過程中,需要注意是否使用了視圖方式

這里說的視圖,指的就是一個具有限制的集合對象,只是把原有數據展現出來給你看,例如不可更改視圖,子視圖等等,這些視圖對于原對象具有不同的操作權限。

Arrays.asList() 為例,它把數組轉成集合時,不能修改其修改集合相關的內容。它的add/remove/clear方法會拋出UnsupportedOperationException

上述代碼可以證明可以通過set方法修改元素的值,原有數組相應位置的值同時也會被修改,但是不能進行修改元素個數的任何操作,否則就會拋異常。

有的人可能就會問了,返回的是ArrayList類,為什么不能對這個集合進行修改呢?

因為這個ArrayList并不是我們平常使用的ArrayList類,這里是個冒牌貨,是Arrays工具類中的一個內部類而已。

這個類非常的簡單,僅提供了改和查相關方法的實現,讓我們來看一下:

至于增刪的操作會拋出會拋出UnsupportedOperationException,是在這個假類的父類AbstractList中實現的

所以當你的業務場景中,數組轉成集合之后,如果可能會對集合進行增和刪的操作,請使用真ArrayList來創建一個新集合。

List list = new java.util.ArrayList(Arrays.asList(數組對象))
集合世界中的 fail-fast 機制

fail-fast 機制 集合世界中比較常見的錯誤檢測機制,防止在對集合進行遍歷過程當中,出現意料之外的修改,會通過Unchecked 異常暴力的反應出來。

實現的方式就是:

當前線程會維護一個計數比較器,即 expectedModCount,記錄已經修改的次數。在進入遍歷時,會把實時修改次數 modCount賦值給 expectedModCount,如果這兩個數據不相等,則拋出異常。

java.util下的集合類都是屬于fail-fast的,而相對應的,j.u.c下的集合類都是fail-safe,fail-safe在之后會介紹。

需要注意的是,即使不是多線程環境,如果單線程違反了規則,同樣也有可能會拋出改異常。比如ArrayList.subList()場景,比如foreach loop 中對集合進行add/remove操作。

ArrayList.sublist() 有什么坑?

subList()場景在《阿里開發手冊》上也是強制要求重點注意的一個規定。

List masterList = new ArrayList();
// ... 對 masterList 進行一系列的set()操作,此處省略
List branchList = masterList.subList(0,3);

如上述場景,當我們需要從一個主列表master中獲取子列表branch時,原集合元素個數的修改,會導致子列表的遍歷、增加、刪除均會產生ConcurrentModificationException

foreach 循環里為什么不能進行元素的 remove/add 操作?

這也是《阿里開發手冊》中對集合處理的一個強制規約。

原因在于,foreach循環這樣的寫法,其實是Java本身給我們的一個語法糖,當你對編譯之后class文件進行反編譯之后,你會發現,增強的for循環,其實是依賴了while循環和Iterator實現的。

Iterator iterator = list.iterator();
do
{
    if(!iterator.hasNext())
        break;
    Object obj = iterator.next();
    // 業務邏輯 瞎編的
    if(canExecute()) {
        list.remove(object)    
    }
} while(true);

在增強for循環中,集合遍歷是通過iterator進行的。

foreach循環這里要注意哦,你如果在foreach循環中調用了 集合的add/remove 方法,最后編譯出來的還是調用的邏輯是沒有變化的。

而在增強for循環中,集合遍歷是通過iterator進行的。

沖突點就發生了,ArrayList 和 LinkedList中 add/remove方法的源碼中,雖然實現不一定相同,但是都會調用modCount++,這行代碼,當你通過iterator進行迭代時,每一次調用next()方法,都會調用一次checkForComodification()方法檢查集合在遍歷過程當中被修改。

關鍵就在于集合自帶的add/remove方法不會去更新迭代器自身的expectedModCount值啊。

手冊里面為什么讓你使用Iterator的add/remove方法?因為除了調用對應集合的對應add/remove方法的同時,它還會去修改自身的expectedModCount值.

一言以蔽之,會拋出ConcurrentModificationException異常,是因為我們的代碼中使用了增強for循環,而在增強for循環中,集合遍歷是通過iterator進行的,但是元素的add/remove卻是直接使用的集合類自己的方法。這就導致iterator在遍歷的時候,會發現有一個元素在自己不知不覺的情況下就被刪除/添加了,就會拋出一個異常,用來提示用戶,可能發生了并發修改

上述案例應引起對刪除元素時的 fail-fast 警覺。我們可以使用Iterator機制進行遍歷時的刪除,如果是多線程并發情況的話,還需要在Iterator遍歷時加鎖,如下源碼。

Iterator iterator = list.iterator();
while(it.hasNext()) {
    synchronized(對象) {
        String item = iterator.next();
        if (刪除元素的條件) {
            iterator.remove();
        }
    }
}

或者,可以直接使用JUC下對應的線程安全集合,CopyOnWriteArrayList來代替。使用迭代器遍歷的時候就不用額外加鎖,也不會拋出ConcurrentModificationException異常。

集合世界中的 fail-safe 機制

與 fail-fast 相對應的,就是 fail-safe 機制;在J.U.C包中集合都是有這種機制實現的。

fail-safe 指的是:在安全的副本(或者沒有提供修改操作的正本)上進行遍歷,集合修改和副本的遍歷是沒有任何關系的,但是缺點也很明顯,就是讀取不到最新的數據

這也是 CAP 理論中 C (Consistency) 和 A (Availability) 的矛盾,即一致性與可用性之間的矛盾。

CAP 定理的含義 -- 阮一峰
copy-on-write 機制

Copy-on-write 是解決并發的的一種思路,也是指的是實行讀寫分離,如果執行的是寫操作,則復制一個新集合,在新集合內添加或者刪除元素。待一切修改完成之后,再將原集合的引用指向新的集合

這樣的好處就是,可以高并發地對COW進行讀和遍歷操作,而不需要加鎖。因為當前集合不會添加任何元素。

前面我們有提到過線程安全的集合Vector,但是Vector的加鎖粒度太大,性能差,所以在并發環境下,推薦JUC包下的的CopyOnWriteArrayList來代替。CopyOnWriteArrayList就是COW家族中的一員。

一般我們認為,CopyOnWriteArrayList 是 同步List 的替代品,CopyOnWriteArraySet 是同步Set 的替代品。

By the way,關于寫時復制(copy-on-write)的這種思想,這種機制,并不是始于Java集合之中,在Linux、Redis、文件系統中都有相應思想的設計,是一種計算機程序設計領域的優化策略。

詳見本篇文章 COW奶牛!Copy On Write機制了解一下。

CopyOnWriteArrayList

前面講的實際大多是概念性的東西,下面詳細剖析下CopyOnWriteArrayList ,讀一讀部分源碼,并且探討幾個在學習過程中的疑問。

關鍵知識點

核心理念就是讀寫分離。

寫操作在一個復制的數組上進行,讀操作還是在原始操作上進行,讀寫分離,互不影響。

寫操作需要加鎖,防止并發寫入時導致數據丟失。

寫操作結束之后需要把 原始數組 指向新的復制數組。

讀寫操作

以寫 - add() 方法 和 讀 - get() 方法為例

通過代碼我們可以知道:寫操作加鎖,防止并發寫入時導致數據丟失,并復制一個新數組,增加操作在新數組上完成,將array指向到新數組中,最后解鎖。

至于讀操作,則是直接讀取array數組中的元素。

遍歷 - COWIterator

到現在,實際上還是沒有解釋為什么CopyOnWriteArrayList 在遍歷時,對其進行修改而不拋出異常?

前面我們知道,不管是foreach 循環還是Iterator方式遍歷,實際上都是使用Iterator遍歷。那么就直接來看下CopyOnWriteArrayList 的iterator()方法。

     public Iterator iterator() {
        return new COWIterator(getArray(), 0);
    }

可以看到對應的迭代器是COWIterator,看這個名字就可以知道這個是基于COW機制的,那么具體呢?

可以看到COWIterator的構造方法,將集合的array數組傳入,實際上就是COWIterator內部維護了一個對象指向集合的數組。

也就是說你使用COWIterator進行遍歷的時候,如果你修改了集合,集合內部的array就指向了新的一個數組對象,而COWIterator內部的那個array還是指向初始化時傳進來的舊數組,所以不會拋異常,因為舊數組永遠沒變過。

缺點 和 使用時需要注意的點

看完上面的解析,大概就能知道CopyOnWriteArrayList 在使用過程中的一些缺點了(實際上就是COW機制的缺點):

內存占用:因為CopyOnWriteArrayList 的每次寫操作,都會復制一個新集合,所以如果對其進行頻繁寫入,會在短時間內造成大量的內存占用。

數據一致性:這個前面提到過,再提一遍,CopyOnWrite容器只能保證數據的最終一致性,不能保證數據的實時一致性

使用時注意的點:

盡量在讀多寫少的場景下去使用CopyOnWriteArrayList

盡量設置合理的容量初始值,因為擴容代價大

使用批量刪除或批量添加方法,如addAll()或removeAll()操作,在高并發請求下,可以攢一下要添加或者刪除的元素,避免增加一個元素復制整個集合的情況

提問 Q: 為什么使用 final ReentrantLock lock = this.lock這樣的寫法?

我在看CopyOnWriteArrayList 源碼的時候,發現寫操作相關的方法內部,都是先將實例變量的lock對象引用賦值給方法的局部變量,然后再進行鎖操作。

我那時候就納悶了很久,為什么要這么寫?直接調用實例中lock對象進行鎖操作不是就可以了嗎?為什么要“多此一舉”呢?

查閱了Stack Overflow上相關的問題才知道,這實際上就是小小的性能優化技巧。

理論上,訪問局部變量比訪問字段更快,也可能只占用更小的字節碼。 但是HotSpot編譯器實際上可以優化對寄存器調用的字段訪問,所以這種寫法和直接訪問字段目前來說應該沒有什么差別。

btw,CopyOnWriteArrayList 是jdk1.5之后引進的。體現了Doug Lea的性能優化的極致追求。

實際上目前的JVM性能優化的技術,兩種寫法的性能已經是沒有差別了。

在JDK11 中,這個實際上已經無用的操作,已經被刪去了。

最后

本章的內容到這里結束了,希望能對你有所幫助。如果有什么想要探討的隨時歡迎評論區留言。

參考

《碼出高效》

《阿里巴巴Java開發手冊》

github cs-note

https://juejin.im/post/5c8717...

Why CopyOnWriteArrayList use getArray() to access an array reference?

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/75415.html

相關文章

  • ArrayList源碼解析之fail-fast機制深入理解

    摘要:當多個線程對同一個集合的內容進行操作時,就可能會產生事件。當某一個線程遍歷的過程中,的內容被另外一個線程所改變了就會拋出異常,產生事件。在線程在遍歷過程中的某一時刻,線程執行了,并且線程刪除了中的節點。 概要 前面,我們已經學習了ArrayList。接下來,我們以ArrayList為例,對Iterator的fail-fast機制進行了解。 1 fail-fast簡介 fail-fast...

    NikoManiac 評論0 收藏0
  • 【一文系列】一文了解java常用集合類(含源碼)

    摘要:包含兩個重要的成員和。對于多線程環境,且可能同時被多個線程操作,此時,應該使用同步的類如。小于等于且大于,代表用戶創建了一個,但是使用的構造函數為或或,導致為,為,為用戶指定的的初始容量。本質上是數組單向鏈表紅黑樹的數據結構如下圖。 一、List 1、ArrayList ① 關鍵源碼 // 默認初始化為空數組 public ArrayList() { this.elementD...

    iliyaku 評論0 收藏0
  • 快速失敗(fail-fast)與安全失敗(fail-safe)

    摘要:注意,迭代器的快速失敗行為無法得到保證,因為一般來說,不可能對是否出現不同步并發修改做出任何硬性保證。快速失敗迭代器會盡最大努力拋出。 fail-fast與fail-safe 在Collection集合的各個類中,有線程安全和線程不安全這2大類的版本。 對于線程不安全的類,并發情況下可能會出現fail-fast情況;而線程安全的類,可能出現fail-safe的情況。 一、并發修改 當一...

    imtianx 評論0 收藏0
  • Java集合總結【面試題+腦圖】,將知識點一網打盡!

    摘要:而在集合中,值僅僅是一個對象罷了該對象對本身而言是無用的。將這篇文章作為集合的總結篇,但覺得沒什么好寫就回答一些面試題去了,找了一會面試題又覺得不夠系統。 前言 聲明,本文用的是jdk1.8 花了一個星期,把Java容器核心的知識過了一遍,感覺集合已經無所畏懼了!!(哈哈哈....),現在來總結一下吧~~ 回顧目錄: Collection總覽 List集合就這么簡單【源碼剖析】 Ma...

    yearsj 評論0 收藏0
  • fail-fast與fail-safe在Java集合應用

    摘要:與在迭代器中的設計在中,最典型的與就是關于迭代器的設計。缺點是,迭代器不能正確及時的反應集合中的內容,而且一定程度上也增加了內存的消耗。 fail-fast與fail-safe簡介 如果一個系統,當有異常或者錯誤發生時就立即中斷執行,這種設計稱之為fail-fast。相反如果我們的系統可以在某種異常或者錯誤發生時繼續執行,不會被中斷,這種設計稱之為fail-safe。 fail-fas...

    Drummor 評論0 收藏0

發表評論

0條評論

young.li

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<