- 資訊首頁(yè) > 開(kāi)發(fā)技術(shù) > 編程語(yǔ)言 >
- Java并發(fā)編程之同步容器
同步容器主要分兩類(lèi),一種是Vector這樣的普通類(lèi),一種是通過(guò)Collections的工廠(chǎng)方法創(chuàng )建的內部類(lèi)
雖然很多人都對同步容器的性能低有偏見(jiàn),但它也不是一無(wú)是處,在這里我們插播一條阿里巴巴的開(kāi)發(fā)手冊規范:
高并發(fā)時(shí),同步調用應該去考量鎖的性能損耗。能用無(wú)鎖數據結構,就不要用鎖;能鎖區塊,就不要鎖整個(gè)方法體;能用對象鎖,就不要用類(lèi)鎖。
可以看到,只有在高并發(fā)才會(huì )考慮到鎖的性能問(wèn)題,所以在一些小而全的系統中,同步容器還是有用武之地的(當然也可以考慮并發(fā)容器,后面章節再討論)
定義:就是把容器類(lèi)同步化,這樣我們在并發(fā)中使用容器時(shí),就不用手動(dòng)同步,因為內部已經(jīng)自動(dòng)同步了
例子:比如Vector就是一個(gè)同步容器類(lèi),它的同步化就是把內部的所有方法都上鎖(有的重載方法沒(méi)上鎖,但是最終調用的方法還是有鎖的)
源碼:Vector.add
// 通過(guò)synchronized為add方法上鎖 public synchronized boolean add(E e) { modCount++; ensureCapacityHelper(elementCount + 1); elementData[elementCount++] = e; return true; }
同步容器主要分兩類(lèi):
1.普通類(lèi):Vector、Stack、HashTable
2.內部類(lèi):Collections創(chuàng )建的內部類(lèi),比如Collections.SynchronizedList、 Collections.SynchronizedSet等
那這兩種有沒(méi)有區別呢?
當然是有的,剛開(kāi)始的時(shí)候(Java1.0)只有第一種同步容器(Vector等)
但是因為Vector這種類(lèi)太局氣了,它就想著(zhù)把所有的東西都弄過(guò)來(lái)自己搞(Vector通過(guò)toArray轉為己有,HashTable通過(guò)putAll轉為己有);
源碼:Vector構造函數
public Vector(Collection<? extends E> c) { // 這里通過(guò)toArray將傳來(lái)的集合 轉為己有 elementData = c.toArray(); elementCount = elementData.length; // c.toArray might (incorrectly) not return Object[] (see 6260652) if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, elementCount, Object[].class); }
所以就有了第二種同步容器類(lèi)(通過(guò)工廠(chǎng)方法創(chuàng )建的內部容器類(lèi)),它就比較聰明了,它只是把原有的容器進(jìn)行包裝(通過(guò)this.list = list直接指向需要同步的容器),然后局部加鎖,這樣一來(lái),即生成了線(xiàn)程安全的類(lèi),又不用太費力;
源碼:Collections.SynchronizedList構造函數
SynchronizedList(List<E> list) { super(list); // 這里只是指向傳來(lái)的list,不轉為己有,后面的相關(guān)操作還是基于原有的list集合 this.list = list; }
他們之間的區別如下:
這里我們重點(diǎn)說(shuō)下鎖的對象:
源碼:Collections.SynchronizedCollection構造函數
final Collection<E> c; // Backing Collection // 這個(gè)就是鎖的對象 final Object mutex; // Object on which to synchronize SynchronizedCollection(Collection<E> c) { this.c = Objects.requireNonNull(c); // 初始化為 this mutex = this; } SynchronizedCollection(Collection<E> c, Object mutex) { this.c = Objects.requireNonNull(c); this.mutex = Objects.requireNonNull(mutex); }
這里要注意一點(diǎn)就是,內部類(lèi)的迭代器沒(méi)有同步(Vector的迭代器有同步),需要手動(dòng)加鎖來(lái)同步
源碼:Vector.Itr.next 迭代方法(有上鎖)
public E next() { synchronized (Vector.this) { checkForComodification(); int i = cursor; if (i >= elementCount) throw new NoSuchElementException(); cursor = i + 1; return elementData(lastRet = i); } }
源碼:Collections.SynchronizedCollection.iterator 迭代器(沒(méi)上鎖)
public Iterator<E> iterator() { // 這里會(huì )直接實(shí)現類(lèi)的迭代器(比如ArrayList,它里面的迭代器肯定是沒(méi)上鎖的) return c.iterator(); // Must be manually synched by user! }
因為普通的容器類(lèi)(比如ArrayList)是線(xiàn)程不安全的,如果是在并發(fā)中使用,我們就需要手動(dòng)對其加鎖才會(huì )安全,這樣的話(huà)就很麻煩;
所以就有了同步容器,它來(lái)幫我們自動(dòng)加鎖
下面我們用代碼來(lái)對比下
線(xiàn)程不安全的類(lèi):ArrayList
public class SyncCollectionDemo { private List<Integer> listNoSync; public SyncCollectionDemo() { this.listNoSync = new ArrayList<>(); } public void addNoSync(int temp){ listNoSync.add(temp); } public static void main(String[] args) throws InterruptedException { SyncCollectionDemo demo = new SyncCollectionDemo(); // 創(chuàng )建10個(gè)線(xiàn)程 for (int i = 0; i < 10; i++) { // 每個(gè)線(xiàn)程執行100次添加操作 new Thread(()->{ for (int j = 0; j < 1000; j++) { demo.addNoSync(j); } }).start(); } } }
上面的代碼看似沒(méi)問(wèn)題,感覺(jué)就算有問(wèn)題也應該是插入的順序比較亂(多線(xiàn)程交替插入)
但實(shí)際上運行會(huì )發(fā)現,可能會(huì )報錯數組越界,如下所示:
原因有二:
因為ArrayList.add操作沒(méi)有加鎖,導致多個(gè)線(xiàn)程可以同時(shí)執行add操作add操作時(shí),如果發(fā)現list的容量不足,會(huì )進(jìn)行擴容,但是由于多個(gè)線(xiàn)程同時(shí)擴容,就會(huì )出現擴容不足的問(wèn)題
源碼:ArrayList.grow擴容
// 擴容方法 private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; // 這里可以看到,每次擴容增加一半的容量 int newCapacity = oldCapacity + (oldCapacity >> 1); if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: elementData = Arrays.copyOf(elementData, newCapacity); }
可以看到,擴容是基于之前的容量進(jìn)行的,因此如果多個(gè)線(xiàn)程同時(shí)擴容,那擴容基數就不準確了,結果就會(huì )有問(wèn)題
線(xiàn)程安全的類(lèi):Collections.SynchronizedList
/** * <p> * 同步容器類(lèi):為什么要有它 * </p> * * @author: JavaLover * @time: 2021/5/3 */ public class SyncCollectionDemo { private List<Integer> listSync; public SyncCollectionDemo() { // 這里包裝一個(gè)空的ArrayList this.listSync = Collections.synchronizedList(new ArrayList<>()); } public void addSync(int j){ // 內部是同步操作: synchronized (mutex) {return c.add(e);} listSync.add(j); } public static void main(String[] args) throws InterruptedException { SyncCollectionDemo demo = new SyncCollectionDemo(); for (int i = 0; i < 10; i++) { new Thread(()->{ for (int j = 0; j < 100; j++) { demo.addSync(j); } }).start(); } TimeUnit.SECONDS.sleep(1); // 輸出1000 System.out.println(demo.listSync.size()); } }
輸出正確,因為現在A(yíng)rrayList被Collections包裝成了一個(gè)線(xiàn)程安全的類(lèi)
這就是為啥會(huì )有同步容器的原因:因為同步容器使得并發(fā)編程時(shí),線(xiàn)程更加安全
一般來(lái)說(shuō),都是先說(shuō)優(yōu)點(diǎn),再說(shuō)缺點(diǎn)
但是我們這次先說(shuō)優(yōu)點(diǎn)
優(yōu)點(diǎn):
缺點(diǎn)(是的,優(yōu)點(diǎn)已經(jīng)說(shuō)完了):
ConcurrentModificationException
,一般出現在當某個(gè)線(xiàn)程在遍歷容器時(shí),其他線(xiàn)程恰好修改了這個(gè)容器的長(cháng)度為啥第三點(diǎn)是缺點(diǎn)呢?
因為它只能作為一個(gè)建議,告訴我們有并發(fā)修改異常,但是不能保證每個(gè)并發(fā)修改都會(huì )爆出這個(gè)異常
爆出這個(gè)異常的前提如下:
源碼:Vector.Itr.checkForComodification 檢查容器修改次數
final void checkForComodification() { // modCount:容器的長(cháng)度變化次數, expectedModCount:期望的容器的長(cháng)度變化次數 if (modCount != expectedModCount) throw new ConcurrentModificationException(); }
那什么情況下并發(fā)修改不會(huì )爆出異常呢?有兩種:
1.遍歷沒(méi)加鎖的情況:對于第二種同步容器(Collections內部類(lèi))來(lái)說(shuō),假設線(xiàn)程A修改了modCount的值,但是沒(méi)有同步到線(xiàn)程B,那么線(xiàn)程B遍歷就不會(huì )發(fā)生異常(但實(shí)際上問(wèn)題已經(jīng)存在了,只是暫時(shí)沒(méi)有出現)
2.依賴(lài)線(xiàn)程執行順序的情況:對于所有的同步容器來(lái)說(shuō),假設線(xiàn)程B已經(jīng)遍歷完了容器,此時(shí)線(xiàn)程A才開(kāi)始遍歷修改,那么也不會(huì )發(fā)生異常
代碼就不貼了,大家感興趣的可以直接寫(xiě)幾個(gè)線(xiàn)程遍歷試試,多運行幾次,應該就可以看到效果(不過(guò)第一種情況也是基于理論分析,實(shí)際代碼我這邊也沒(méi)跑出來(lái))
根據阿里巴巴的開(kāi)發(fā)規范:不要在 foreach 循環(huán)里進(jìn)行元素的 remove/add 操作。remove 元素請使用 Iterator方式,如果并發(fā)操作,需要對 Iterator 對象加鎖。
這里解釋下,關(guān)于List.remove和Iterator.remove的區別
源碼:ArrayList.remove移除元素操作
public E remove(int index) { rangeCheck(index); // 1. 這里修改了 modCount modCount++; E oldValue = elementData(index); int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--size] = null; // clear to let GC do its work return oldValue; }
源碼:ArrayList.Itr.remove迭代器移除元素操作
public void remove() { if (lastRet < 0) throw new IllegalStateException(); checkForComodification(); try { // 1. 這里調用上面介紹的list.romove,修改modCount ArrayList.this.remove(lastRet); cursor = lastRet; lastRet = -1; // 2. 這里再同步更新 expectedModCount expectedModCount = modCount; } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } }
由于同步容器的這些缺點(diǎn),于是就有了并發(fā)容器(下期來(lái)介紹)
多用在并發(fā)編程,但是并發(fā)量又不是很大的場(chǎng)景,比如一些簡(jiǎn)單的個(gè)人博客系統(具體多少并發(fā)量算大,這個(gè)也是分很多情況而論的,并不是說(shuō)每秒處理超過(guò)多少個(gè)請求,就說(shuō)是高并發(fā),還要結合吞吐量、系統響應時(shí)間等多個(gè)因素一起考慮)
具體點(diǎn)來(lái)說(shuō)的話(huà),有以下幾個(gè)場(chǎng)景:
什么是同步容器:就是把容器類(lèi)同步化,這樣我們在并發(fā)中使用容器時(shí),就不用手動(dòng)同步,因為內部已經(jīng)自動(dòng)同步了
為什么要有同步容器:因為普通的容器類(lèi)(比如ArrayList)是線(xiàn)程不安全的,如果是在并發(fā)中使用,我們就需要手動(dòng)對其加鎖才會(huì )安全,這樣的話(huà)就很太麻煩;所以就有了同步容器,它來(lái)幫我們自動(dòng)加鎖
同步容器的優(yōu)缺點(diǎn):
缺點(diǎn) 復合操作,還是不安全,性能差快速失敗機制,只適合bug調試
同步容器的使用場(chǎng)景
多用在并發(fā)量不是很大的場(chǎng)景,比如個(gè)人博客、后臺系統等
具體點(diǎn)來(lái)說(shuō),有以下幾個(gè)場(chǎng)景:
到此這篇關(guān)于Java并發(fā)編程之同步容器的文章就介紹到這了,更多相關(guān)Java同步容器內容請搜索腳本之家以前的文章或繼續瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng )、來(lái)自本網(wǎng)站內容采集于網(wǎng)絡(luò )互聯(lián)網(wǎng)轉載等其它媒體和分享為主,內容觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如侵犯了原作者的版權,請告知一經(jīng)查實(shí),將立刻刪除涉嫌侵權內容,聯(lián)系我們QQ:712375056,同時(shí)歡迎投稿傳遞力量。
Copyright ? 2009-2022 56dr.com. All Rights Reserved. 特網(wǎng)科技 特網(wǎng)云 版權所有 特網(wǎng)科技 粵ICP備16109289號
域名注冊服務(wù)機構:阿里云計算有限公司(萬(wàn)網(wǎng)) 域名服務(wù)機構:煙臺帝思普網(wǎng)絡(luò )科技有限公司(DNSPod) CDN服務(wù):阿里云計算有限公司 百度云 中國互聯(lián)網(wǎng)舉報中心 增值電信業(yè)務(wù)經(jīng)營(yíng)許可證B2
建議您使用Chrome、Firefox、Edge、IE10及以上版本和360等主流瀏覽器瀏覽本網(wǎng)站