java多线程基本概述(二十六)——免锁容器
容器是所有編程中的基礎(chǔ)工具,這其中也包括并發(fā)編程。出于這個(gè)原因,像Vector和Hashtable這類早期容器具有許多synchronized方法,當(dāng)他們用于非多線程的應(yīng)用程序中時(shí),便會(huì)導(dǎo)致不可接受的開(kāi)銷。在Java1.2中,新的容器類庫(kù)是不同步的,并且Collections類提供了各種static的同步的裝飾方法,從而來(lái)同步不同類型的容器。盡管這是一種改進(jìn),因?yàn)樗鼓憧梢赃x擇在你的容器中是否要使用同步,但是這種開(kāi)銷仍舊是基于synchronized加鎖機(jī)制的。Java SE5特別添加了新容器,通過(guò)使用更靈巧的技術(shù)消除加鎖,從而提高線程的安全的性能。
這些免鎖容器背后的通用策略是:對(duì)容器的修改可以與讀取操作同時(shí)發(fā)生,只要讀取者只能看到完成修改后的結(jié)果即可。修改是在容器數(shù)據(jù)結(jié)構(gòu)的某一部分的一個(gè)單獨(dú)的副本(有時(shí)是整個(gè)數(shù)據(jù)結(jié)構(gòu)的副本)上執(zhí)行的,并且這個(gè)副本在修改過(guò)程中是不可視的。只有當(dāng)修改完成時(shí),被修改的結(jié)構(gòu)才會(huì)自動(dòng)地與主數(shù)據(jù)結(jié)構(gòu)進(jìn)行交換,之后讀取者就可以看到這個(gè)修改了。
在CopyOnWriteArrayList中,寫入將導(dǎo)致創(chuàng)建整個(gè)底層數(shù)組的副本,而源數(shù)組將保留在原地,使得復(fù)制的數(shù)組在被修改時(shí),讀取操作可以安全的執(zhí)行。當(dāng)修改完成時(shí),一個(gè)原子性操作將把新的數(shù)組換入,使得新的讀取操作可以看到這個(gè)新的修改。CopyOnWriteArrayList的好處之一是當(dāng)多個(gè)迭代器同時(shí)遍歷和修改這個(gè)列表時(shí),不會(huì)拋出ConcurrentModificationException,因此你不必編寫特殊的代碼去防范這種異常,就像你以前必須作的那樣。
迭代器上進(jìn)行的元素更改操作(remove、set?和?add)不受支持。這些方法將拋出?UnsupportedOperationException。
CopyOnWriteArraySet將使用CopyOnWriteArrayList來(lái)實(shí)現(xiàn)其免鎖行為。
ConcurrentHashMap和ConcurrentLinkedQueue使用了類似的技術(shù),允許并發(fā)的讀取和寫入,但是容器中只有部分內(nèi)容而不是整個(gè)容器可以被復(fù)制和修改。然而,任何修改在完成之前,讀取者仍舊不能看到它們。ConcurrentHashMap不會(huì)拋出ConcurrentModificationException異常。
??CopyOnWriteArrayList適合用在“讀多,寫少”的“并發(fā)”應(yīng)用中,換句話說(shuō),它適合使用在讀操作遠(yuǎn)遠(yuǎn)大于寫操作的場(chǎng)景里,比如緩存。它不存在“擴(kuò)容”的概念,每次寫操作(add or remove)都要copy一個(gè)副本,在副本的基礎(chǔ)上修改后改變array引用,所以稱為“CopyOnWrite”,因此在寫操作是加鎖,并且對(duì)整個(gè)list的copy操作時(shí)相當(dāng)耗時(shí)的,過(guò)多的寫操作不推薦使用該存儲(chǔ)結(jié)構(gòu)。
?
一個(gè)ConcurrentHashMap由多個(gè)segment組成,每一個(gè)segment都包含了一個(gè)HashEntry數(shù)組的hashtable, 每一個(gè)segment包含了對(duì)自己的hashtable的操作,比如get,put,replace等操作,這些操作發(fā)生的時(shí)候,對(duì)自己的hashtable進(jìn)行鎖定。由于每一個(gè)segment寫操作只鎖定自己的hashtable,所以可能存在多個(gè)線程同時(shí)寫的情況,性能無(wú)疑好于只有一個(gè)hashtable鎖定的情況。
?
?
源碼分析 在ConcurrentHashMap的remove,put操作還是比較簡(jiǎn)單的,都是將remove或者put操作交給key所對(duì)應(yīng)的segment去做的,所以當(dāng)幾個(gè)操作不在同一個(gè)segment的時(shí)候就可以并發(fā)的進(jìn)行。
public V remove(Object key) {int hash = hash(key.hashCode());return segmentFor(hash).remove(key, hash, null);}而segment中的remove操作除了加鎖之外和HashMap中的remove操作基本無(wú)異
V remove(Object key, int hash, Object value) {lock();try {int c = count - 1;HashEntry<K,V>[] tab = table;int index = hash & (tab.length - 1);HashEntry<K,V> first = tab[index];HashEntry<K,V> e = first;while (e != null && (e.hash != hash || !key.equals(e.key)))e = e.next;V oldValue = null;if (e != null) {V v = e.value;if (value == null || value.equals(v)) {oldValue = v;// All entries following removed node can stay// in list, but all preceding ones need to be// cloned.++modCount;HashEntry<K,V> newFirst = e.next;for (HashEntry<K,V> p = first; p != e; p = p.next)newFirst = new HashEntry<K,V>(p.key, p.hash,newFirst, p.value);tab[index] = newFirst;count = c; // write-volatile }}return oldValue;} finally {unlock();}}?
package tij;import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors;/*** Created by huaox on 2017/4/21.**/ public class CopyOnWriteArrayListDemo {private static class ReadTask implements Runnable {List<String> list;ReadTask(List<String> list) {this.list = list;}public void run() {for (String str : list) {System.out.println(str);}}}private static class WriteTask implements Runnable {List<String> list;int index;WriteTask(List<String> list, int index) {this.list = list;this.index = index;}public void run() {list.remove(index);list.add(index, "write_" + index);}}public void run() {final int NUM = 5;List<String> list = new ArrayList<String>();for (int i = 0; i < NUM; i++) {list.add("main_" + i);}ExecutorService executorService = Executors.newFixedThreadPool(NUM);for (int i = 0; i < NUM; i++) {executorService.execute(new ReadTask(list));executorService.execute(new WriteTask(list, i));}executorService.shutdown();}public static void main(String[] args) {new CopyOnWriteArrayListDemo().run();} }輸出結(jié)果:
Exception in thread "pool-1-thread-3" java.util.ConcurrentModificationException main_0 main_1at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901) main_2 main_3at java.util.ArrayList$Itr.next(ArrayList.java:851) main_4 main_0 main_1 write_2 main_3 main_4 main_0 main_1 write_2 write_3 main_4 main_0 write_0 write_1at tij.CopyOnWriteArrayListDemo$ReadTask.run(CopyOnWriteArrayListDemo.java:22) write_2 write_3at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) write_4at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)at java.lang.Thread.run(Thread.java:745)Process finished with exit code 0免鎖容器的源碼為賦值底層數(shù)組,然后賦新值,改引用
public boolean add(E e) {final ReentrantLock lock = this.lock;lock.lock();try {Object[] elements = getArray();int len = elements.length;Object[] newElements = Arrays.copyOf(elements, len + 1);newElements[len] = e;setArray(newElements);return true;} finally {lock.unlock();}}修改為CopyOnWriteArrayList后
public void run() {final int NUM = 5;List<String> list = new CopyOnWriteArrayList<>();for (int i = 0; i < NUM; i++) {list.add("main_" + i);}ExecutorService executorService = Executors.newFixedThreadPool(NUM);for (int i = 0; i < NUM; i++) {executorService.execute(new ReadTask(list));executorService.execute(new WriteTask(list, i));}executorService.shutdown();}輸出結(jié)果:
main_0 main_1 main_2 main_3 main_4 main_0 main_1 write_2 main_3 main_4 write_0 main_1 write_2 write_3 main_4 write_0 main_1 write_2 write_3 write_4 write_0 write_1 write_2 write_3 write_4Process finished with exit code 0?
轉(zhuǎn)載于:https://www.cnblogs.com/soar-hu/p/6742694.html
總結(jié)
以上是生活随笔為你收集整理的java多线程基本概述(二十六)——免锁容器的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 2Python全栈之路系列之基于sock
- 下一篇: 指针例题