Java下的線程安全容器
在編寫多線程的Java程序時(shí)贿条,難免會(huì)使用到各種各樣的容器。
Java本身提供了許多線程安全的容器,開(kāi)發(fā)者可以按照需求利用這些容器來(lái)進(jìn)行相應(yīng)的開(kāi)發(fā)。
但Java提供的線程安全容器種類非常多院究, 拿 Set 來(lái)舉例,線程安全的 Set 至少有:
- CopyOnWriteArraySet
- ConcurrentSkipListSet
- Collections.synchronizedSet(Set set)
- Collections.newSetFromMap(new ConcurrentHashMap())
- ......
這些容器之間的區(qū)別是什么玷或?作為開(kāi)發(fā)者儡首,又該如何對(duì)這些容器進(jìn)行選擇呢片任?
這確實(shí)是一件令博主很頭疼的一件事情偏友。查閱了網(wǎng)絡(luò)上的一些資料之后,總算是有了少許的領(lǐng)悟对供。下面是一些整理:
總的來(lái)說(shuō)位他,這些線程安全的容器主要分為兩類:
- 同步容器類
- 并發(fā)容器類
1. 同步容器類
前面提到的 Collections.synchronizedSet(Set set) 就屬于這個(gè)分類。它實(shí)現(xiàn)線程安全的方法是:
在原始的 *Set *中的每一個(gè)方法的外部包裹一個(gè) synchronized 塊产场,且只允許調(diào)用者訪問(wèn)這些被 synchronized 關(guān)鍵字同步起來(lái)的方法鹅髓。
這樣一來(lái),就意味著京景,不可能有兩個(gè)或以上的方法被同時(shí)執(zhí)行 (沖突的調(diào)用會(huì)被阻塞起來(lái)窿冯,直到前一個(gè)方法執(zhí)行完成)
同步容器類的特點(diǎn)就是:在任何時(shí)刻,只允許一個(gè)線程訪問(wèn)該容器的狀態(tài)确徙。這從它的實(shí)現(xiàn)方式來(lái)看也是顯而易見(jiàn)的醒串。
也因?yàn)槭沁@樣执桌,這類容器一般不具備并發(fā)能力,也就是說(shuō)芜赌,如果多個(gè)線程同時(shí)使用該容器仰挣,它們對(duì)容器的操作實(shí)際上是串行的。
對(duì)于同步類容器缠沈,有一點(diǎn)需要額外注意:雖然同步類容器是線程安全的容器膘壶,在使用其迭代器(iterator)時(shí),仍需要使用 synchronized 關(guān)鍵字在外部對(duì)其進(jìn)行同步洲愤。
考慮下述情況:
List<Student> students=Collections.synchronizedList(new ArrayList<Student>());
for(Student student:students)
doSomething(student);
假設(shè)線程A在對(duì) students 進(jìn)行迭代操作的中途颓芭,線程B刪除了 students 中的一個(gè)元素(這完全是有可能的,因?yàn)榫€程A并沒(méi)有在整個(gè)迭代操作過(guò)程中獲得對(duì) students 的鎖)柬赐,
就會(huì)拋出 ConcurrentModificationExceptions 畜伐。
為了避免這種情況,應(yīng)該使用 synchronized 關(guān)鍵字對(duì)迭代操作部分的代碼進(jìn)行加鎖:
List<Student> students= Collections.synchronizedList(new ArrayList<Student>());
synchronized (students) {
for(Student student:students)
doSomething(student);
}
除了迭代操作躺率,當(dāng)有多個(gè)線程對(duì)容器進(jìn)行訪問(wèn)玛界、刪除元素等常見(jiàn)操作時(shí)也可能會(huì)引發(fā)類似的問(wèn)題,比如拋出 ArrayIndexOutOfBoundException 悼吱。
在進(jìn)行這類操作時(shí)慎框,應(yīng)該將其變成原子的。
當(dāng)對(duì)并發(fā)度的要求很低后添,而又希望對(duì)容器的修改可以馬上為其它線程可見(jiàn)時(shí)笨枯,可以考慮使用這類容器。
2.并發(fā)容器
并發(fā)容器允許多個(gè)線程同時(shí)對(duì)容器進(jìn)行訪問(wèn)(往往有一定的限制)遇西,相比同步類容器馅精,在多線程環(huán)境下,并發(fā)容器往往具有更優(yōu)的運(yùn)行效率粱檀。
Copy-On-Write 寫時(shí)復(fù)制容器:
前面提到的CopyOnWriteArraySet是一種寫時(shí)復(fù)制容器洲敢,屬于并發(fā)容器。他實(shí)現(xiàn)并發(fā)的原理非常的簡(jiǎn)單茄蚯,甚至從它的名字就能夠看出來(lái):
CopyOnWriteArraySet內(nèi)部維護(hù)著一個(gè)數(shù)組压彭,當(dāng)要對(duì)元素進(jìn)行修改時(shí),它就將該數(shù)組復(fù)制一份渗常,并在復(fù)制出來(lái)的副本上進(jìn)行修改壮不,
修改完成后再將原數(shù)組的引用指向新的數(shù)組。
如果這個(gè)時(shí)候同時(shí)有其它進(jìn)程對(duì)這個(gè)容器進(jìn)行讀操作皱碘,它們?cè)L問(wèn)的實(shí)際上是修改前的舊版本询一。因此讀操作是不需要對(duì)容器上鎖的。
這是一種讀寫分離的思想,從而避免了在讀者和寫者之間進(jìn)行同步的必要(當(dāng)然寫者和寫者直接還是需要同步的)健蕊。
可以從其實(shí)現(xiàn)中看出缓醋,CopyOnWrite 容器存在連個(gè)比較顯著的不足:
- 內(nèi)存占用問(wèn)題:因?yàn)閷懖僮餍枰獜?fù)制一份原數(shù)據(jù)的副本,因此會(huì)在內(nèi)存中同時(shí)駐扎兩份數(shù)據(jù)绊诲,增加了幾乎一倍的開(kāi)銷
- 數(shù)據(jù)一致性問(wèn)題:因?yàn)樽x寫分離送粱,寫時(shí)復(fù)制容器不能保證數(shù)據(jù)的實(shí)時(shí)一致性。也就是說(shuō)寫入的數(shù)據(jù)不能保證馬上就能被讀到掂之。
如果數(shù)據(jù)量比較小抗俄、預(yù)期的讀操作遠(yuǎn)比寫操作多,可以考慮使用 CopyOnWrite 容器
其他并發(fā)類容器
前面提到的ConcurrentHashMap和 同步容器 以及 寫時(shí)復(fù)制容器 都不同世舰。同步類容器动雹,比如 HashTable,為了保證線程安全跟压,
選擇了對(duì)整個(gè)map進(jìn)行加鎖(map-wide)胰蝠。這樣一來(lái),只要一個(gè)線程獲得了鎖震蒋,別的線程就必須等待持有鎖的線程退出茸塞,這樣就極大地降低了CPU的利用率。
ConcurrentHashMap對(duì)此進(jìn)行了優(yōu)化查剖,相比HashTable钾虐,ConcurrentHashMap使用了更細(xì)粒度的鎖。
首先了解下ConcurrentHashMap的結(jié)構(gòu)模型(引用自developerWorks?):
ConcurrentHashMap 類中包含兩個(gè)靜態(tài)內(nèi)部類 HashEntry 和 Segment笋庄。
HashEntry 用來(lái)封裝映射表的鍵 / 值對(duì)效扫,其中鍵和值都不允許為null;
Segment 用來(lái)充當(dāng)鎖的角色直砂,每個(gè) Segment 對(duì)象守護(hù)整個(gè)散列映射表的若干個(gè)桶菌仁。
每個(gè)桶是由若干個(gè) HashEntry 對(duì)象鏈接起來(lái)的鏈表。
一個(gè) ConcurrentHashMap 實(shí)例中包含由若干個(gè) Segment 對(duì)象組成的數(shù)組静暂。
示意圖:
對(duì)ConcurrentHashMap的讀操作一般不用加鎖就可以操作完成济丘,除非讀出的value值為null(說(shuō)明發(fā)生了重排序),才需要加鎖重讀籍嘹。
對(duì)ConcurrentHashMap的寫操作需要進(jìn)行加鎖闪盔,但有別于HashTable等同步容器類,進(jìn)行ConcurrentHashMap的寫操作時(shí)只需要對(duì)相關(guān)的segment加鎖即可辱士,
其余未上鎖的segment仍然可以被其它線程訪問(wèn)。
寫線程對(duì)容器的修改(增加听绳、刪除颂碘、替換等)不會(huì)影響其它并發(fā)讀線程對(duì)容器的訪問(wèn)。
當(dāng)有很大的數(shù)據(jù)集需要處理,且對(duì)并發(fā)度要求比較高時(shí)头岔,可以考慮使用這類容器
總結(jié)
Java下的線程安全容器有很多種塔拳,對(duì)這些容器進(jìn)行學(xué)習(xí)和探討,能夠幫助我們?cè)陂_(kāi)發(fā)過(guò)程中選擇到更加符合需求的一種容器峡竣。
這些不同類型的容器靠抑,對(duì)于并發(fā)和同步的處理都各有特點(diǎn),我們可以從中獲得啟發(fā)适掰,將其中有用的思想應(yīng)用到自己的項(xiàng)目中去颂碧。