1.集合包
? ? 集合包最常用的有Collection和Map兩個接口的實現(xiàn)類帕涌,Colleciton用于存放多個單對象,Map用于存放Key-Value形式的鍵值對下面。
? Collection中最常用的又分為兩種類型的接口:List和Set复颈,兩者最明顯的差別為List支持放入重復(fù)的元素,而Set不支持沥割。
List最常用的實現(xiàn)類有:ArrayList耗啦、LinkedList、Vector及Stack机杜;Set接口常用的實現(xiàn)類有:HashSet帜讲、TreeSet。
1.1 ArrayList
? ArrayList基于數(shù)組方式實現(xiàn)椒拗,默認(rèn)構(gòu)造器通過調(diào)用ArrayList(int)來完成創(chuàng)建似将,傳入的值為10,實例化了一個Object數(shù)組蚀苛,并將此數(shù)組賦給了當(dāng)前實例的elementData屬性在验,此Object數(shù)組的大小即為傳入的initialCapacity,因此調(diào)用空構(gòu)造器的情況下會創(chuàng)建一個大小為10的Object數(shù)組枉阵。
插入對象:add(E)
????基于已有元素數(shù)量加1作為名叫minCapacity的變量译红,比較此值和Object數(shù)組的大小,若大于數(shù)組值兴溜,那么先將當(dāng)前Object數(shù)組值賦給一個數(shù)組對象侦厚,接著產(chǎn)生一個鑫的數(shù)組容量值。此值的計算方法為當(dāng)前數(shù)組值*1.5+1拙徽,如得出的容量值仍然小于minCapacity刨沦,那么就以minCapacity作為新的容量值,調(diào)用Arrays.copyOf來生成新的數(shù)組對象膘怕。
????還提供了add(int,E)這樣的方法將元素直接插入指定的int位置上想诅,將目前index及其后的數(shù)據(jù)都往后挪一位,然后才能將指定的index位置的賦值為傳入的對象岛心,這種方式要多付出一次復(fù)制數(shù)組的代價来破。還提供了addAll
?刪除對象:remove(E)
? ?這里調(diào)用了faseRemove方法將index后的對象往前復(fù)制一位,并將數(shù)組中的最后一個元素的值設(shè)置為null忘古,即釋放了對此對象的引用徘禁。 還提供了remove(int)方法來刪除指定位置的對象,remove(int)的實現(xiàn)比remove(E)多了一個數(shù)組范圍的檢測髓堪,但少了對象位置的查找送朱,因此性能會更好娘荡。
獲取單個對象:get(int)
遍歷對象:iterator()
判斷對象是否存在:contains(E)
?總結(jié):
????1,ArrayList基于數(shù)組方式實現(xiàn)驶沼,無容量的限制炮沐;
????2,ArrayList在執(zhí)行插入元素時可能要擴容回怜,在刪除元素時并不會減小數(shù)組的容量(如希望相應(yīng)的縮小數(shù)組容量大年,可以調(diào)用ArrayList的trimToSize()),在查找元素時要遍歷數(shù)組鹉戚,對于非null的元素采取equals的方式尋找鲜戒;
????3,ArrayList是非線程安全的抹凳。
1.2 LinkedList
????LinkedList基于雙向鏈表機制遏餐,所謂雙向鏈表機制,就是集合中的每個元素都知道其前一個元素及其后一個元素的位置赢底。在LinkedList中失都,以一個內(nèi)部的Entry類來代表集合中的元素,元素的值賦給element屬性幸冻,Entry中的next屬性指向元素的后一個元素粹庞,Entry中的previous屬性指向元素的前一個元素,基于這樣的機制可以快速實現(xiàn)集合中元素的移動洽损。
總結(jié):
????1庞溜,LinkedList基于雙向鏈表機制實現(xiàn);
????2碑定,LinkedList在插入元素時流码,須創(chuàng)建一個新的Entry對象,并切換相應(yīng)元素的前后元素的引用延刘;在查找元素時漫试,須遍歷鏈表;在刪除元素時碘赖,要遍歷鏈表驾荣,找到要刪除的元素,然后從鏈表上將此元素刪除即可普泡,此時原有的前后元素改變引用連在一起播掷;
????3,LinkedList是非線程安全的撼班。
1.3 Vector
? ? 其add叮趴、remove、get(int)方法都加了synchronized關(guān)鍵字权烧,默認(rèn)創(chuàng)建一個大小為10的Object數(shù)組眯亦,并將capacityIncrement設(shè)置為0。容量擴充策略:如果capacityIncrement大于0般码,則將Object數(shù)組的大小擴大為現(xiàn)有size加上capacityIncrement的值妻率;如果capacity等于或小于0,則將Object數(shù)組的大小擴大為現(xiàn)有size的兩倍板祝,這種容量的控制策略比ArrayList更為可控宫静。
????Vector是基于Synchronized實現(xiàn)的線程安全的ArrayList,但在插入元素時容量擴充的機制和ArrayList稍有不同券时,并可通過傳入capacityIncrement來控制容量的擴充孤里。
1.4 Stack
????Stack繼承于Vector,在其基礎(chǔ)上實現(xiàn)了Stack所要求的后進(jìn)先出(LIFO)的彈出與壓入操作橘洞,其提供了push捌袜、pop、peek三個主要的方法:
????push操作通過調(diào)用Vector中的addElement來完成炸枣;
????pop操作通過調(diào)用peek來獲取元素虏等,并同時刪除數(shù)組中的最后一個元素;
????peek操作通過獲取當(dāng)前Object數(shù)組的大小适肠,并獲取數(shù)組上的最后一個元素霍衫。
1.5 HashSet
????默認(rèn)構(gòu)造創(chuàng)建一個HashMap對象
add(E):調(diào)用HashMap的put方法來完成此操作,將需要增加的元素作為Map中的key侯养,value則傳入一個之前已創(chuàng)建的Object對象敦跌。
remove(E):調(diào)用HashMap的remove(E)方法完成此操作。
contains(E):HashMap的containsKey
iterator():調(diào)用HashMap的keySet的iterator方法逛揩。
HashSet不支持通過get(int)獲取指定位置的元素柠傍,只能自行通過iterator方法來獲取。
總結(jié):
????1息尺,HashSet基于HashMap實現(xiàn)携兵,無容量限制;
????2搂誉,HashSet是非線程安全的徐紧。
1.6 TreeSet
????TreeSet和HashSet的主要不同在于TreeSet對于排序的支持,TreeSet基于TreeMap實現(xiàn)炭懊。
1.7?HashMap
????HashMap空構(gòu)造并级,將loadFactor設(shè)為默認(rèn)的0.75,threshold設(shè)置為12侮腹,并創(chuàng)建一個大小為16的Entry對象數(shù)組嘲碧。
????基于數(shù)組+鏈表的結(jié)合體(鏈表散列)實現(xiàn),將key-value看成一個整體父阻,存放于Entity[]數(shù)組愈涩,put的時候根據(jù)key hash后的hashcode和數(shù)組length-1按位與的結(jié)果值判斷放在數(shù)組的哪個位置望抽,如果該數(shù)組位置上若已經(jīng)存放其他元素,則在這個位置上的元素以鏈表的形式存放履婉。如果該位置上沒有元素則直接存放煤篙。
當(dāng)系統(tǒng)決定存儲HashMap中的key-value對時,完全沒有考慮Entry中的value毁腿,僅僅只是根據(jù)key來計算并決定每個Entry的存儲位置辑奈。我們完全可以把Map集合中的value當(dāng)成key的附屬丑罪,當(dāng)系統(tǒng)決定了key的存儲位置之后本鸣,value隨之保存在那里即可灸眼。get取值也是根據(jù)key的hashCode確定在數(shù)組的位置予权,在根據(jù)key的equals確定在鏈表處的位置嘲驾。
1 while (capacity < initialCapacity)2? ? ? capacity <<= 1;
以上代碼保證了初始化時HashMap的容量總是2的n次方精拟,即底層數(shù)組的長度總是為2的n次方席赂。它通過h & (table.length -1)?來得到該對象的保存位姐赡,若length為奇數(shù)值唐片,則與運算產(chǎn)生相同結(jié)果丙猬,便會形成鏈表,盡可能的少出現(xiàn)鏈表才能提升hashMap的效率费韭,所以這是hashMap速度上的優(yōu)化茧球。
擴容resize():
當(dāng)HashMap中的元素越來越多的時候,hash沖突的幾率也就越來越高星持,因為數(shù)組的長度是固定的抢埋。所以為了提高查詢的效率,就要對HashMap的數(shù)組進(jìn)行擴容督暂,而在HashMap數(shù)組擴容之后揪垄,最消耗性能的點就出現(xiàn)了:原數(shù)組中的數(shù)據(jù)必須重新計算其在新數(shù)組中的位置,并放進(jìn)去逻翁,這就是resize饥努。那么HashMap什么時候進(jìn)行擴容呢?當(dāng)HashMap中的元素個數(shù)超過數(shù)組大小*loadFactor時八回,就會進(jìn)行數(shù)組擴容酷愧,loadFactor的默認(rèn)值為0.75,這是一個折中的取值缠诅。
負(fù)載因子衡量的是一個散列表的空間的使用程度溶浴,負(fù)載因子越大表示散列表的裝填程度越高,反之愈小管引。負(fù)載因子越大士败,對空間的利用更充分,然而后果是查找效率的降低褥伴;如果負(fù)載因子太小谅将,那么散列表的數(shù)據(jù)將過于稀疏漾狼,對空間造成嚴(yán)重浪費。
HashMap的實現(xiàn)中戏自,通過threshold字段來判斷HashMap的最大容量邦投。threshold就是在此loadFactor和capacity對應(yīng)下允許的最大元素數(shù)目,超過這個數(shù)目就重新resize擅笔,以降低實際的負(fù)載因子。默認(rèn)的的負(fù)載因子0.75是對空間和時間效率的一個平衡選擇屯援。
initialCapacity*2猛们,成倍擴大容量,HashMap(int initialCapacity, float loadFactor):以指定初始容量狞洋、指定的負(fù)載因子創(chuàng)建一個?HashMap弯淘。不設(shè)定參數(shù),則初始容量值為16吉懊,默認(rèn)的負(fù)載因子為0.75庐橙,不宜過大也不宜過小,過大影響效率借嗽,過小浪費空間态鳖。擴容后需要重新計算每個元素在數(shù)組中的位置,是一個非常消耗性能的操作恶导,所以如果我們已經(jīng)預(yù)知HashMap中元素的個數(shù)浆竭,那么預(yù)設(shè)元素的個數(shù)能夠有效的提高HashMap的性能。
? ? ?HashTable數(shù)據(jù)結(jié)構(gòu)的原理大致一樣惨寿,區(qū)別在于put邦泄、get時加了同步關(guān)鍵字,而且HashTable不可存放null值裂垦。
在高并發(fā)時可以使用ConcurrentHashMap顺囊,其內(nèi)部使用鎖分段技術(shù),維持這鎖Segment的數(shù)組蕉拢,在數(shù)組中又存放著Entity[]數(shù)組特碳,內(nèi)部hash算法將數(shù)據(jù)較均勻分布在不同鎖中。
總結(jié):
????1企量,HashMap采用數(shù)組方式存儲key测萎、value構(gòu)成的Entry對象,無容量限制届巩;
????2硅瞧,HashMap基于key hash尋找Entry對象存放到數(shù)組的位置,對于hash沖突采用鏈表的方式解決恕汇;
????3腕唧,HashMap在插入元素時可能會擴大數(shù)組的容量或辖,在擴大容量時須要重新計算hash,并復(fù)制對象到新的數(shù)組中枣接;
????4颂暇,HashMap是非線程安全的。
詳細(xì)說明:http://zhangshixi.iteye.com/blog/672697
1.8 TreeMap
????TreeMap基于紅黑樹的實現(xiàn)但惶,因此它要求一定要有key比較的方法耳鸯,要么傳入Comparator實現(xiàn),要么key對象實現(xiàn)Comparable借口膀曾。在put操作時县爬,基于紅黑樹的方式遍歷,基于comparator來比較key應(yīng)放在樹的左邊還是右邊添谊,如找到相等的key财喳,則直接替換掉value。
2.并發(fā)包
?jdk5.0一很重要的特性就是增加了并發(fā)包java.util.concurrent.*斩狱,在說具體的實現(xiàn)類或接口之前耳高,這里先簡要說下Java內(nèi)存模型、volatile變量及AbstractQueuedSynchronizer(以下簡稱AQS同步器)所踊,這些都是并發(fā)包眾多實現(xiàn)的基礎(chǔ)泌枪。
Java內(nèi)存模型
????描述了線程內(nèi)存與主存見的通訊關(guān)系。定義了線程內(nèi)的內(nèi)存改變將怎樣傳遞到其他線程的規(guī)則污筷,同樣也定義了線程內(nèi)存與主存進(jìn)行同步的細(xì)節(jié)工闺,也描述了哪些操作屬于原子操作及操作間的順序。
代碼順序規(guī)則:
????一個線程內(nèi)的每個動作happens-before同一個線程內(nèi)在代碼順序上在其后的所有動作.
volatile變量規(guī)則:
????對一個volatile變量的讀瓣蛀,總是能看到(任意線程)對這個volatile變量最后的寫入.
傳遞性:
????如果A happens-before B, B happens-before C, 那么A happens-before C.? ??
volatile
當(dāng)我們聲明共享變量為volatile后陆蟆,對這個變量的讀/寫將會很特別。理解volatile特性的一個好方法是:把對volatile變量的單個讀/寫惋增,看成是使用同一個監(jiān)視器鎖對這些單個讀/寫操作做了同步叠殷。
監(jiān)視器鎖的happens-before規(guī)則保證釋放監(jiān)視器和獲取監(jiān)視器的兩個線程之間的內(nèi)存可見性,這意味著對一個volatile變量的讀诈皿,總是能看到(任意線程)對這個volatile變量最后的寫入林束。
簡而言之,volatile變量自身具有下列特性:
可見性稽亏。對一個volatile變量的讀壶冒,總是能看到(任意線程)對這個volatile變量最后的寫入。
原子性:對任意單個volatile變量的讀/寫具有原子性截歉,但類似于volatile++這種復(fù)合操作不具有原子性胖腾。
volatile寫的內(nèi)存語義如下:
當(dāng)寫一個volatile變量時,JMM會把該線程對應(yīng)的本地內(nèi)存中的共享變量刷新到主內(nèi)存。
volatile讀的內(nèi)存語義如下:
當(dāng)讀一個volatile變量時咸作,JMM會把該線程對應(yīng)的本地內(nèi)存置為無效锨阿。線程接下來將從主內(nèi)存中讀取共享變量。
下面對volatile寫和volatile讀的內(nèi)存語義做個總結(jié):
線程A寫一個volatile變量记罚,實質(zhì)上是線程A向接下來將要讀這個volatile變量的某個線程發(fā)出了(其對共享變量所在修改的)消息墅诡。
線程B讀一個volatile變量,實質(zhì)上是線程B接收了之前某個線程發(fā)出的(在寫這個volatile變量之前對共享變量所做修改的)消息桐智。
線程A寫一個volatile變量末早,隨后線程B讀這個volatile變量,這個過程實質(zhì)上是線程A通過主內(nèi)存向線程B發(fā)送消息酵使。
鎖釋放-獲取與volatile的讀寫具有相同的內(nèi)存語義荐吉,
鎖釋放的內(nèi)存語義如下:
????當(dāng)線程釋放鎖時,JMM會把該線程對應(yīng)的本地內(nèi)存中的共享變量刷新到主內(nèi)存口渔。
鎖獲取的內(nèi)存語義如下:
????當(dāng)線程獲取鎖時,JMM會把該線程對應(yīng)的本地內(nèi)存置為無效穿撮,從而使得被監(jiān)視器保護的臨界區(qū)代碼必須要從主內(nèi)存中讀取共享變量缺脉。
下面對鎖釋放和鎖獲取的內(nèi)存語義做個總結(jié):
線程A釋放一個鎖,實質(zhì)上是線程A向接下來將要獲取這個鎖的某個線程發(fā)出了(線程A對共享變量所做修改的)消息悦穿。
線程B獲取一個鎖攻礼,實質(zhì)上是線程B接收了之前某個線程發(fā)出的(在釋放這個鎖之前對共享變量所做修改的)消息。
線程A釋放鎖栗柒,隨后線程B獲取這個鎖礁扮,這個過程實質(zhì)上是線程A通過主內(nèi)存向線程B發(fā)送消息。
示例:
classVolatileExample{
intx =0;
volatileintb =0;
privatevoidwrite(){
x =5;
b =1;
? ? }
privatevoidread(){
intdummy = b;
while(x !=5) {
? ? ? ? }
? ? }
publicstaticvoidmain(String[] args)throwsException{
finalVolatileExample example =newVolatileExample();
Thread thread1 =newThread(newRunnable() {
publicvoidrun(){
? ? ? ? ? ? ? ? example.write();
? ? ? ? ? ? }
? ? ? ? });
Thread thread2 =newThread(newRunnable() {
publicvoidrun(){
? ? ? ? ? ? ? ? example.read();
? ? ? ? ? ? }
? ? ? ? });
? ? ? ? thread1.start();
? ? ? ? thread2.start();
? ? ? ? thread1.join();
? ? ? ? thread2.join();
? ? }
}
若thread1先于thread2執(zhí)行瞬沦,則程序執(zhí)行流程分析如上圖所示太伊,thread2讀的結(jié)果是dummy=1,x=5所以不會進(jìn)入死循環(huán)逛钻。
但并不能保證兩線程的執(zhí)行順序僚焦,若thread2先于thread1執(zhí)行,則程序在兩線程join中斷之前的結(jié)果為:因為b變量的類型是volatile曙痘,故thread1寫之后芳悲,thread2即可讀到b變量的值發(fā)生變化,
而x是普通變量边坤,故最后情況是dummy=1名扛,但thread2的讀操作因為x=0而進(jìn)入死循環(huán)中。
????在JSR-133之前的舊Java內(nèi)存模型中茧痒,雖然不允許volatile變量之間重排序肮韧,但舊的Java內(nèi)存模型仍然會允許volatile變量與普通變量之間重排序。JSR-133則增強了volatile的內(nèi)存語義:嚴(yán)格限制編譯器(在編譯期)和處理器(在運行期)對volatile變量與普通變量的重排序,確保volatile的寫-讀和監(jiān)視器的釋放-獲取一樣惹苗,具有相同的內(nèi)存語義殿较。限制重排序是通過內(nèi)存屏障實現(xiàn)的,具體可見JMM的描述桩蓉。
????由于volatile僅僅保證對單個volatile變量的讀/寫具有原子性淋纲,而監(jiān)視器鎖的互斥執(zhí)行的特性可以確保對整個臨界區(qū)代碼的執(zhí)行具有原子性。在功能上院究,監(jiān)視器鎖比volatile更強大洽瞬;在可伸縮性和執(zhí)行性能上,volatile更有優(yōu)勢业汰。如果讀者想在程序中用volatile代替監(jiān)視器鎖伙窃,請一定謹(jǐn)慎。
AbstractQueuedSynchronizer (AQS)
????AQS使用一個整型的volatile變量(命名為state)來維護同步狀態(tài)样漆,這是接下來實現(xiàn)大部分同步需求的基礎(chǔ)为障。提供了一個基于FIFO隊列,可以用于構(gòu)建鎖或者其他相關(guān)同步裝置的基礎(chǔ)框架放祟。使用的方法是繼承鳍怨,子類通過繼承同步器并需要實現(xiàn)它的方法來管理其狀態(tài),管理的方式就是通過類似acquire和release的方式來操縱狀態(tài)跪妥。然而多線程環(huán)境中對狀態(tài)的操縱必須確保原子性鞋喇,因此子類對于狀態(tài)的把握,需要使用這個同步器提供的以下三個方法對狀態(tài)進(jìn)行操作:
java.util.concurrent.locks.AbstractQueuedSynchronizer.getState()
java.util.concurrent.locks.AbstractQueuedSynchronizer.setState(int)
java.util.concurrent.locks.AbstractQueuedSynchronizer.compareAndSetState(int, int)
子類推薦被定義為自定義同步裝置的內(nèi)部類眉撵,同步器自身沒有實現(xiàn)任何同步接口侦香,它僅僅是定義了若干acquire之類的方法來供使用。該同步器即可以作為排他模式也可以作為共享模式纽疟,當(dāng)它被定義為一個排他模式時罐韩,其他線程對其的獲取就被阻止,而共享模式對于多個線程獲取都可以成功仰挣。
同步器是實現(xiàn)鎖的關(guān)鍵伴逸,利用同步器將鎖的語義實現(xiàn),然后在鎖的實現(xiàn)中聚合同步器膘壶〈砗可以這樣理解:鎖的API是面向使用者的,它定義了與鎖交互的公共行為颓芭,而每個鎖需要完成特定的操作也是透過這些行為來完成的(比如:可以允許兩個線程進(jìn)行加鎖顷锰,排除兩個以上的線程),但是實現(xiàn)是依托給同步器來完成亡问;同步器面向的是線程訪問和資源控制官紫,它定義了線程對資源是否能夠獲取以及線程的排隊等操作肛宋。鎖和同步器很好的隔離了二者所需要關(guān)注的領(lǐng)域,嚴(yán)格意義上講束世,同步器可以適用于除了鎖以外的其他同步設(shè)施上(包括鎖)酝陈。
同步器的開始提到了其實現(xiàn)依賴于一個FIFO隊列,那么隊列中的元素Node就是保存著線程引用和線程狀態(tài)的容器毁涉,每個線程對同步器的訪問沉帮,都可以看做是隊列中的一個節(jié)點。
對于一個獨占鎖的獲取和釋放有如下偽碼可以表示:
獲取一個排他鎖
while(獲取鎖) {
if(獲取到) {
退出while循環(huán)
}else{
if(當(dāng)前線程沒有入隊列) {
? ? ? ? ? ? 那么入隊列
? ? ? ? }
? ? ? ? 阻塞當(dāng)前線程
? ? }
}
釋放一個排他鎖
1 if (釋放成功) {2? ? 刪除頭結(jié)點3? ? 激活原頭結(jié)點的后繼節(jié)點4 }
示例:
下面通過一個排它鎖的例子來深入理解一下同步器的工作原理贫堰,而只有掌握同步器的工作原理才能更加深入了解其他的并發(fā)組件穆壕。
排他鎖的實現(xiàn),一次只能一個線程獲取到鎖:
publicclassMuteximplementsLock,java.io.Serializable{
// 內(nèi)部類其屏,自定義同步器
privatestaticclassSyncextendsAbstractQueuedSynchronizer{
// 是否處于占用狀態(tài)
protectedbooleanisHeldExclusively(){
returngetState() ==1;
? ? ? }
// 當(dāng)狀態(tài)為0的時候獲取鎖
publicbooleantryAcquire(intacquires){
assertacquires ==1;// Otherwise unused
if(compareAndSetState(0,1)) {
? ? ? ? ? setExclusiveOwnerThread(Thread.currentThread());
returntrue;
? ? ? ? }
returnfalse;
? ? ? }
// 釋放鎖喇勋,將狀態(tài)設(shè)置為0
protectedbooleantryRelease(intreleases){
assertreleases ==1;// Otherwise unused
if(getState() ==0)thrownewIllegalMonitorStateException();
setExclusiveOwnerThread(null);
setState(0);
returntrue;
? ? ? }
// 返回一個Condition,每個condition都包含了一個condition隊列
ConditionnewCondition(){returnnewConditionObject(); }
? ? }
// 僅需要將操作代理到Sync上即可
privatefinalSync sync =newSync();
publicvoidlock(){ sync.acquire(1); }
publicbooleantryLock(){returnsync.tryAcquire(1); }
publicvoidunlock(){ sync.release(1); }
publicConditionnewCondition(){returnsync.newCondition(); }
publicbooleanisLocked(){returnsync.isHeldExclusively(); }
publicbooleanhasQueuedThreads(){returnsync.hasQueuedThreads(); }
publicvoidlockInterruptibly()throwsInterruptedException{
sync.acquireInterruptibly(1);
? ? }
publicbooleantryLock(longtimeout, TimeUnit unit)
throwsInterruptedException {
returnsync.tryAcquireNanos(1, unit.toNanos(timeout));
? ? }
? }
可以看到Mutex將Lock接口均代理給了同步器的實現(xiàn)偎行。使用方將Mutex構(gòu)造出來后川背,調(diào)用lock獲取鎖,調(diào)用unlock將鎖釋放蛤袒。
獲取鎖渗常,acquire(int arg)的主要邏輯包括:
1. 嘗試獲取(調(diào)用tryAcquire更改狀態(tài)汗盘,需要保證原子性);
????在tryAcquire方法中適用了同步器提供的對state操作的方法询一,利用compareAndSet保證只有一個線程能夠?qū)顟B(tài)進(jìn)行成功修改隐孽,而沒有成功修改的線程將進(jìn)入sync隊列排隊。
2. 如果獲取不到健蕊,將當(dāng)前線程構(gòu)造成節(jié)點Node并加入sync隊列菱阵;
????進(jìn)入隊列的每個線程都是一個節(jié)點Node,從而形成了一個雙向隊列缩功,類似CLH隊列晴及,這樣做的目的是線程間的通信會被限制在較小規(guī)模(也就是兩個節(jié)點左右)。
3. 再次嘗試獲取嫡锌,如果沒有獲取到那么將當(dāng)前線程從線程調(diào)度器上摘下虑稼,進(jìn)入等待狀態(tài)。
釋放鎖势木,release(int arg)的主要邏輯包括:
1. 嘗試釋放狀態(tài)蛛倦;
????tryRelease能夠保證原子化的將狀態(tài)設(shè)置回去,當(dāng)然需要使用compareAndSet來保證啦桌。如果釋放狀態(tài)成功之后溯壶,就會進(jìn)入后繼節(jié)點的喚醒過程。
2. 喚醒當(dāng)前節(jié)點的后繼節(jié)點所包含的線程。
????通過LockSupport的unpark方法將休眠中的線程喚醒且改,讓其繼續(xù)acquire狀態(tài)验烧。
回顧整個資源的獲取和釋放過程:
在獲取時,維護了一個sync隊列又跛,每個節(jié)點都是一個線程在進(jìn)行自旋碍拆,而依據(jù)就是自己是否是首節(jié)點的后繼并且能夠獲取資源;
在釋放時效扫,僅僅需要將資源還回去倔监,然后通知一下后繼節(jié)點并將其喚醒。
這里需要注意菌仁,隊列的維護(首節(jié)點的更換)是依靠消費者(獲取時)來完成的浩习,也就是說在滿足了自旋退出的條件時的一刻,這個節(jié)點就會被設(shè)置成為首節(jié)點济丘。
隊列里的節(jié)點線程的禁用和喚醒是通過LockSupport的park()及unpark()谱秽,調(diào)用的unsafe、底層也是native的實現(xiàn)摹迷。
關(guān)于java lock的淺析可見:http://jm-blog.aliapp.com/?p=414
共享模式和以上的獨占模式有所區(qū)別疟赊,分別調(diào)用acquireShared(int arg)和releaseShared(int arg)獲取共享模式的狀態(tài)。
以文件的查看為例峡碉,如果一個程序在對其進(jìn)行讀取操作近哟,那么這一時刻,對這個文件的寫操作就被阻塞鲫寄,相反吉执,這一時刻另一個程序?qū)ζ溥M(jìn)行同樣的讀操作是可以進(jìn)行的。如果一個程序在對其進(jìn)行寫操作地来,
那么所有的讀與寫操作在這一時刻就被阻塞戳玫,直到這個程序完成寫操作。
以讀寫場景為例未斑,描述共享和獨占的訪問模式咕宿,如下圖所示:
上圖中,紅色代表被阻塞蜡秽,綠色代表可以通過府阀。
在上述對同步器AbstractQueuedSynchronizer進(jìn)行了實現(xiàn)層面的分析之后,我們通過一個例子來加深對同步器的理解:
設(shè)計一個同步工具载城,該工具在同一時刻肌似,只能有兩個線程能夠并行訪問,超過限制的其他線程進(jìn)入阻塞狀態(tài)诉瓦。
對于這個需求川队,可以利用同步器完成一個這樣的設(shè)定力细,定義一個初始狀態(tài),為2固额,一個線程進(jìn)行獲取那么減1眠蚂,一個線程釋放那么加1,狀態(tài)正確的范圍在[0斗躏,1逝慧,2]三個之間,當(dāng)在0時啄糙,代表再有新的線程對資源進(jìn)行獲取時只能進(jìn)入阻塞狀態(tài)(注意在任何時候進(jìn)行狀態(tài)變更的時候均需要以CAS作為原子性保障)笛臣。由于資源的數(shù)量多于1個,同時可以有兩個線程占有資源隧饼,因此需要實現(xiàn)tryAcquireShared和tryReleaseShared方法沈堡。
publicclassTwinsLockimplementsLock{
privatefinalSync? sync? ? =newSync(2);
privatestaticfinalclassSyncextendsAbstractQueuedSynchronizer{
privatestaticfinallongserialVersionUID? ? = -7889272986162341211L;
Sync(intcount) {
if(count <=0) {
thrownewIllegalArgumentException("count must large than zero.");
? ? ? ? ? ? }
? ? ? ? ? ? setState(count);
? ? ? ? }
publicinttryAcquireShared(intreduceCount){
for(;;) {
intcurrent = getState();
intnewCount = current - reduceCount;
if(newCount <0|| compareAndSetState(current, newCount)) {
returnnewCount;
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? }
publicbooleantryReleaseShared(intreturnCount){
for(;;) {
intcurrent = getState();
intnewCount = current + returnCount;
if(compareAndSetState(current, newCount)) {
returntrue;
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? }
? ? }
publicvoidlock(){
sync.acquireShared(1);
? ? }
publicvoidlockInterruptibly()throwsInterruptedException{
sync.acquireSharedInterruptibly(1);
? ? }
publicbooleantryLock(){
returnsync.tryAcquireShared(1) >=0;
? ? }
publicbooleantryLock(longtime, TimeUnit unit)throwsInterruptedException{
returnsync.tryAcquireSharedNanos(1, unit.toNanos(time));
? ? }
publicvoidunlock(){
sync.releaseShared(1);
? ? }
publicConditionnewCondition(){
returnnull;
? ? }
}
這里我們編寫一個測試來驗證TwinsLock是否能夠正常工作并達(dá)到預(yù)期。
publicclassTwinsLockTest{
@Test
publicvoidtest(){
finalLock lock =newTwinsLock();
classWorkerextendsThread{
publicvoidrun(){
while(true) {
? ? ? ? ? ? ? ? ? ? lock.lock();
try{
Thread.sleep(1000L);
? ? ? ? ? ? ? ? System.out.println(Thread.currentThread());
Thread.sleep(1000L);
}catch(Exception ex) {
}finally{
? ? ? ? ? ? ? ? ? ? ? ? lock.unlock();
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? }
for(inti =0; i <10; i++) {
Worker w =newWorker();
? ? ? ? ? ? w.start();
? ? ? ? }
newThread() {
publicvoidrun(){
while(true) {
try{
Thread.sleep(200L);
? ? ? ? ? ? ? ? ? ? ? ? System.out.println();
}catch(Exception ex) {
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? }.start();
try{
Thread.sleep(20000L);
}catch(InterruptedException e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? }
? ? }
}
上述測試用例的邏輯主要包括:
1. 打印線程
Worker在兩次睡眠之間打印自身線程燕雁,如果一個時刻只能有兩個線程同時訪問诞丽,那么打印出來的內(nèi)容將是成對出現(xiàn)。
2. 分隔線程
不停的打印換行拐格,能讓W(xué)orker的輸出看起來更加直觀僧免。
該測試的結(jié)果是在一個時刻,僅有兩個線程能夠獲得到鎖捏浊,并完成打印懂衩,而表象就是打印的內(nèi)容成對出現(xiàn)。
利用CAS(compare and set)是不會進(jìn)行阻塞的金踪,只會一個返回成功勃痴,一個返回失敗,保證了一致性热康。
CAS操作同時具有volatile讀和volatile寫的內(nèi)存語義。
AQS這部分轉(zhuǎn)載于http://ifeve.com/introduce-abstractqueuedsynchronizer/
?2.1?ConcurrentHashMap
????ConcurrentHashMap是線程安全的HashMap的實現(xiàn)劣领,默認(rèn)構(gòu)造同樣有initialCapacity和loadFactor屬性姐军,不過還多了一個concurrencyLevel屬性,三屬性默認(rèn)值分別為16尖淘、0.75及16奕锌。其內(nèi)部使用鎖分段技術(shù),維持這鎖Segment的數(shù)組村生,在Segment數(shù)組中又存放著Entity[]數(shù)組惊暴,內(nèi)部hash算法將數(shù)據(jù)較均勻分布在不同鎖中。
put操作:并沒有在此方法上加上synchronized趁桃,首先對key.hashcode進(jìn)行hash操作辽话,得到key的hash值肄鸽。hash操作的算法和map也不同,根據(jù)此hash值計算并獲取其對應(yīng)的數(shù)組中的Segment對象(繼承自ReentrantLock)油啤,接著調(diào)用此Segment對象的put方法來完成當(dāng)前操作典徘。
ConcurrentHashMap基于concurrencyLevel劃分出了多個Segment來對key-value進(jìn)行存儲,從而避免每次put操作都得鎖住整個數(shù)組益咬。在默認(rèn)的情況下逮诲,最佳情況下可允許16個線程并發(fā)無阻塞的操作集合對象,盡可能地減少并發(fā)時的阻塞現(xiàn)象幽告。
get(key)
????首先對key.hashCode進(jìn)行hash操作梅鹦,基于其值找到對應(yīng)的Segment對象,調(diào)用其get方法完成當(dāng)前操作冗锁。而Segment的get操作首先通過hash值和對象數(shù)組大小減1的值進(jìn)行按位與操作來獲取數(shù)組上對應(yīng)位置的HashEntry齐唆。在這個步驟中,可能會因為對象數(shù)組大小的改變蒿讥,以及數(shù)組上對應(yīng)位置的HashEntry產(chǎn)生不一致性蝶念,那么ConcurrentHashMap是如何保證的?
????對象數(shù)組大小的改變只有在put操作時有可能發(fā)生芋绸,由于HashEntry對象數(shù)組對應(yīng)的變量是volatile類型的媒殉,因此可以保證如HashEntry對象數(shù)組大小發(fā)生改變,讀操作可看到最新的對象數(shù)組大小摔敛。
????在獲取到了HashEntry對象后廷蓉,怎么能保證它及其next屬性構(gòu)成的鏈表上的對象不會改變呢?這點ConcurrentHashMap采用了一個簡單的方式马昙,即HashEntry對象中的hash桃犬、key、next屬性都是final的行楞,這也就意味著沒辦法插入一個HashEntry對象到基于next屬性構(gòu)成的鏈表中間或末尾攒暇。這樣就可以保證當(dāng)獲取到HashEntry對象后,其基于next屬性構(gòu)建的鏈表是不會發(fā)生變化的子房。
????ConcurrentHashMap默認(rèn)情況下采用將數(shù)據(jù)分為16個段進(jìn)行存儲形用,并且16個段分別持有各自不同的鎖Segment,鎖僅用于put和remove等改變集合對象的操作证杭,基于volatile及HashEntry鏈表的不變性實現(xiàn)了讀取的不加鎖田度。這些方式使得ConcurrentHashMap能夠保持極好的并發(fā)支持,尤其是對于讀遠(yuǎn)比插入和刪除頻繁的Map而言解愤,而它采用的這些方法也可謂是對于Java內(nèi)存模型镇饺、并發(fā)機制深刻掌握的體現(xiàn)。
2.2 ReentrantLock
????在并發(fā)包的開始部分介紹了volatile特性及AQS同步器送讲,而這兩部分正是ReentrantLock實現(xiàn)的基礎(chǔ)奸笤。通過上面AQS的介紹及原理分析惋啃,可知道是以volatile維持的int類型的state值,來判斷線程是執(zhí)行還是在syn隊列中等待揭保。
ReentrantLock的實現(xiàn)不僅可以替代隱式的synchronized關(guān)鍵字肥橙,而且能夠提供超過關(guān)鍵字本身的多種功能。
????這里提到一個鎖獲取的公平性問題秸侣,如果在絕對時間上存筏,先對鎖進(jìn)行獲取的請求一定被先滿足,那么這個鎖是公平的味榛,反之椭坚,是不公平的,也就是說等待時間最長的線程最有機會獲取鎖搏色,也可以說鎖的獲取是有序的善茎。ReentrantLock這個鎖提供了一個構(gòu)造函數(shù),能夠控制這個鎖是否是公平的频轿。
????對于公平和非公平的定義是通過對同步器AbstractQueuedSynchronizer的擴展加以實現(xiàn)的垂涯,也就是tryAcquire的實現(xiàn)上做了語義的控制。
公平和非公平性的更多原理分析見于http://ifeve.com/reentrantlock-and-fairness/
2.3 Condition
????Condition是并發(fā)包中提供的一個接口航邢,典型的實現(xiàn)有ReentrantLock耕赘,ReentrantLock提供了一個mewCondition的方法,以便用戶在同一個鎖的情況下可以根據(jù)不同的情況執(zhí)行等待或喚醒動作膳殷。典型的用法可參考ArrayBlockingQueue的實現(xiàn)操骡,下面來看ReentrantLock中
newCondition的實現(xiàn)。
ReentrantLock.newCondition()
????創(chuàng)建一個AbstractQueuedSynchronizer的內(nèi)部類ConditionObject的對象實例赚窃。
ReentrantLock.newCondition().await()
????將當(dāng)前線程加入此condition的等待隊列中册招,并將線程置為等待狀態(tài)。
ReentrantLock.newCondition().signal()
????從此condition的等待隊列中獲取一個等待節(jié)點勒极,并將節(jié)點上的線程喚醒是掰,如果要喚醒全部等待節(jié)點的線程,則調(diào)用signalAll方法辱匿。
2.4 CopyOnWriteArrayList
????CopyOnWriteArrayList是一個線程安全冀惭、并且在讀操作時無鎖的ArrayList,其具體實現(xiàn)方法如下掀鹅。
CopyOnWriteArrayList()
????和ArrayList不同,此步的做法為創(chuàng)建一個大小為0的數(shù)組媒楼。
add(E)
????add方法并沒有加上synchronized關(guān)鍵字乐尊,它通過使用ReentrantLock來保證線程安全。此處和ArrayList的不同是每次都會創(chuàng)建一個新的Object數(shù)組划址,此數(shù)組的大小為當(dāng)前數(shù)組大小加1扔嵌,將之前數(shù)組中的內(nèi)容復(fù)制到新的數(shù)組中限府,并將
新增加的對象放入數(shù)組末尾,最后做引用切換將新創(chuàng)建的數(shù)組對象賦值給全局的數(shù)組對象痢缎。
remove(E)
????和add方法一樣胁勺,此方法也通過ReentrantLock來保證其線程安全,但它和ArrayList刪除元素采用的方式并不一樣独旷。
????首先創(chuàng)建一個比當(dāng)前數(shù)組小1的數(shù)組署穗,遍歷新數(shù)組,如找到equals或均為null的元素嵌洼,則將之后的元素全部賦值給新的數(shù)組對象案疲,并做引用切換,返回true麻养;如未找到褐啡,則將當(dāng)前的元素賦值給新的數(shù)組對象榛了,最后特殊處理數(shù)組中的最后
一個元素停巷,如最后一個元素等于要刪除的元素,即將當(dāng)前數(shù)組對象賦值為新創(chuàng)建的數(shù)組對象仁期,完成刪除操作许昨,如最后一個元素也不等于要刪除的元素懂盐,那么返回false。
????此方法和ArrayList除了鎖不同外车要,最大的不同在于其復(fù)制過程并沒有調(diào)用System的arrayCopy來完成允粤,理論上來說會導(dǎo)致性能有一定下降。
get(int)????
????此方法非常簡單翼岁,直接獲取當(dāng)前數(shù)組對應(yīng)位置的元素类垫,這種方法是沒有加鎖保護的,因此可能會出現(xiàn)讀到臟數(shù)據(jù)的現(xiàn)象琅坡。但相對而言悉患,性能會非常高,對于寫少讀多且臟數(shù)據(jù)影響不大的場景而言是不錯的選擇榆俺。
iterator()
????調(diào)用iterator方法后創(chuàng)建一個新的COWIterator對象實例售躁,并保存了一個當(dāng)前數(shù)組的快照,在調(diào)用next遍歷時則僅對此快照數(shù)組進(jìn)行遍歷茴晋,因此遍歷此list時不會拋出ConcurrentModificatiedException陪捷。
????與ArrayList的性能對比,在讀多寫少的并發(fā)場景中诺擅,較之ArrayList是更好的選擇市袖,單線程以及多線程下增加元素及刪除元素的性能不比ArrayList好
2.5 CopyOnWriteArraySet
????CopyOnWriteArraySet基于CopyOnWriteArrayList實現(xiàn),其唯一的不同是在add時調(diào)用的是CopyOnWriteArrayList的addIfAbsent方法烁涌。保證了無重復(fù)元素苍碟,但在add時每次都要進(jìn)行數(shù)組的遍歷酒觅,因此性能會略低于上個。
2.6 ArrayBlockingQueue
2.7 ThreadPoolExecutor
與每次需要時都創(chuàng)建線程相比微峰,線程池可以降低創(chuàng)建線程的開銷舷丹,在線程執(zhí)行結(jié)束后進(jìn)行的是回收操作,提高對線程的復(fù)用蜓肆。Java中主要使用的線程池是ThreadPoolExecutor颜凯,此外還有定時的線程池ScheduledThreadPoolExecutor。
Java里面線程池的頂級接口是Executor症杏,但是嚴(yán)格意義上講Executor并不是一個線程池装获,而只是一個執(zhí)行線程的工具。真正的線程池接口是ExecutorService厉颤。
比較重要的幾個類:
ExecutorService真正的線程池接口
ScheduledExecutorService和Time/TimeTask類似穴豫,解決需要任務(wù)重復(fù)執(zhí)行的問題
ThreadPoolExecutorExecutorService的默認(rèn)實現(xiàn)
SchedulesThreadPoolExecutor繼承ThreadPoolExecutor的ScheduledExecutorService接口實現(xiàn),周期性任務(wù)調(diào)度的類實現(xiàn)
要配置一個線程池是比較復(fù)雜的逼友,尤其是對于線程池的原理不是很清楚的情況下精肃,很有可能配置的線程池不是較優(yōu)的,因此在Executors類里面提供了一些靜態(tài)工廠帜乞,生成一些常用的線程池司抱。
1. newSingleThreadExecutor
創(chuàng)建一個單線程的線程池。這個線程池只有一個線程在工作黎烈,也就是相當(dāng)于單線程串行執(zhí)行所有任務(wù)习柠。如果這個唯一的線程因為異常結(jié)束,那么會有一個新的線程來替代它照棋。此線程池保證所有任務(wù)的執(zhí)行順序按照任務(wù)的提交順序執(zhí)行资溃。
2.newFixedThreadPool
創(chuàng)建固定大小的線程池。每次提交一個任務(wù)就創(chuàng)建一個線程烈炭,直到線程達(dá)到線程池的最大大小溶锭。線程池的大小一旦達(dá)到最大值就會保持不變,如果某個線程因為執(zhí)行異常而結(jié)束符隙,那么線程池會補充一個新線程趴捅。
3. newCachedThreadPool
創(chuàng)建一個可緩存的線程池。如果線程池的大小超過了處理任務(wù)所需要的線程霹疫,
那么就會回收部分空閑(60秒不執(zhí)行任務(wù))的線程拱绑,當(dāng)任務(wù)數(shù)增加時,此線程池又可以智能的添加新線程來處理任務(wù)丽蝎。此線程池不會對線程池大小做限制猎拨,線程池大小完全依賴于操作系統(tǒng)(或者說JVM)能夠創(chuàng)建的最大線程大小。
4.newScheduledThreadPool
創(chuàng)建一個大小無限的線程池。此線程池支持定時以及周期性執(zhí)行任務(wù)的需求迟几。
PS:但需要注意使用,newSingleThreadExecutor和newFixedThreadPool將超過處理的線程放在隊列中栏笆,但工作線程較多時类腮,會引起過多內(nèi)存被占用,而后兩者返回的線程池是沒有線程上線的蛉加,所以在使用時需要當(dāng)心蚜枢,創(chuàng)建過多的線程容易引起服務(wù)器的宕機。
使用ThreadPoolExecutor自定義線程池针饥,具體使用時需根據(jù)系統(tǒng)及JVM的配置設(shè)置適當(dāng)?shù)膮?shù)厂抽,下面是一示例:
1 int corePoolSize = Runtime.getRuntime().availableProcessors();2 threadsPool = new ThreadPoolExecutor(corePoolSize, corePoolSize, 10l, TimeUnit.SECONDS,3? ? ? ? ? ? ? ? new LinkedBlockingQueue(2000));
2.8 Future和FutureTask
Future是一個接口,F(xiàn)utureTask是一個具體實現(xiàn)類丁眼。這里先通過兩個場景看看其處理方式及優(yōu)點筷凤。
場景1,
現(xiàn)在通過調(diào)用一個方法從遠(yuǎn)程獲取一些計算結(jié)果,假設(shè)有這樣一個方法:
1 HashMap data = getDataFromRemote();
如果是最傳統(tǒng)的同步方式的使用苞七,我們將一直等待getDataFromRemote()的返回藐守,然后才能繼續(xù)后面的工作。這個函數(shù)是從遠(yuǎn)程獲取數(shù)據(jù)的計算結(jié)果的蹂风,如果需要的時間很長卢厂,并且后面的那部分代碼與這些數(shù)據(jù)沒有關(guān)系的話,阻塞在這里等待結(jié)果就會比較浪費時間惠啄。如何改進(jìn)呢慎恒?
能夠想到的辦法就是調(diào)用函數(shù)后馬上返回,然后繼續(xù)向下執(zhí)行撵渡,等需要用數(shù)據(jù)時再來用或者再來等待這個數(shù)據(jù)融柬。具體實現(xiàn)有兩種方式:一個是用Future,另一個使用回調(diào)姥闭。
Future的用法
1 Future future = getDataFromRemote2();2 //do something3 HashMap data = future.get();
可以看到丹鸿,我們調(diào)用的方法返回一個Future對象,然后接著進(jìn)行自己的處理棚品,后面通過future.get()來獲取真正的返回值靠欢。也即,在調(diào)用了getDataFromRemote2后铜跑,就已經(jīng)啟動了對遠(yuǎn)程計算結(jié)果的獲取门怪,同時自己的線程還在繼續(xù)處理,直到需要時再獲取數(shù)據(jù)锅纺。來看一下getDataFromRemote2的實現(xiàn):
privete FuturegetDataFromRemote2(){
returnthreadPool.submit(newCallable(){
publicHashMapcall()throwsException{
returngetDataFromRemote();
? ? ? ? }
? ? });
}
可以看到掷空,在getDataFromRemote2中還是使用了getDataFromRemote來完成具體操作,并且用到了線程池:把任務(wù)加入到線程池中,把Future對象返回出去坦弟。我們調(diào)用了getDataFromRemote2的線程护锤,然后返回來繼續(xù)下面的執(zhí)行,而背后是另外的線程在進(jìn)行遠(yuǎn)程調(diào)用及等待的工作酿傍。get方法也可設(shè)置超時時間參數(shù)烙懦,而不是一直等下去。
場景2赤炒,
key-value的形式存儲連接氯析,若key存在則獲取,若不存在這個key莺褒,則創(chuàng)建新連接并存儲掩缓。
傳統(tǒng)的方式會使用HashMap來存儲并判斷key是否存在而實現(xiàn)連接的管理。而這在高并發(fā)的時候會出現(xiàn)多次創(chuàng)建連接的現(xiàn)象遵岩。那么新的處理方式又是怎樣呢你辣?
通過ConcurrentHashMap及FutureTask實現(xiàn)高并發(fā)情況的正確性,ConcurrentHashMap的分段鎖存儲滿足數(shù)據(jù)的安全性又不影響性能旷余,F(xiàn)utureTask的run方法調(diào)用Sync.innerRun方法只會執(zhí)行Runnable的run方法一次(即使是高并發(fā)情況)绢记。
2.9 并發(fā)容器
在JDK中,有一些線程不安全的容器正卧,也有一些線程安全的容器蠢熄。并發(fā)容器是線程安全容器的一種,但是并發(fā)容器強調(diào)的是容器的并發(fā)性炉旷,也就是說不僅追求線程安全签孔,還要考慮并發(fā)性,提升在容器并發(fā)環(huán)境下的性能窘行。
加鎖互斥的方式確實能夠方便地完成線程安全饥追,不過代價是降低了并發(fā)性,或者說是串行了罐盔。而并發(fā)容器的思路是盡量不用鎖但绕,比較有代表性的是以CopyOnWrite和Concurrent開頭的幾個容器。CopyOnWrite容器的思路是在更改容器的時候惶看,把容器寫一份進(jìn)行修改捏顺,保證正在讀的線程不受影響,這種方式用在讀多寫少的場景中會非常好纬黎,因為實質(zhì)上是在寫的時候重建了一次容器幅骄。而以Concurrent開頭的容器的具體實現(xiàn)方式則不完全相同,總體來說是盡量保證讀不加鎖本今,并且修改時不影響讀拆座,所以達(dá)到比使用讀寫鎖更高的并發(fā)性能主巍。比如上面所說的ConcurrentHashMap,其他的并發(fā)容器的具體實現(xiàn)挪凑,可直接分析JDK中的源碼孕索。