www.久久久久|狼友网站av天堂|精品国产无码a片|一级av色欲av|91在线播放视频|亚洲无码主播在线|国产精品草久在线|明星AV网站在线|污污内射久久一区|婷婷综合视频网站

當前位置:首頁 > 單片機 > 架構(gòu)師社區(qū)
[導讀]本篇所有示例代碼已更新到 我的Github;本篇文章已收納到我的Java在線文檔 集合,準備團戰(zhàn)。

來源 | 悟空聊架構(gòu)(ID:PassJava666)

一、線程不安全之ArrayList

集合框架有Map和Collection兩大類,Collection下面有List、Set、Queue。List下面有ArrayList、Vector、LinkedList。


JUC并發(fā)包下的集合類Collections有Queue、CopyOnWriteArrayList、CopyOnWriteArraySet、ConcurrentMap


我們先來看看ArrayList。

1.1、ArrayList的底層初始化操作

首先我們來復(fù)習下ArrayList的使用,下面是初始化一個ArrayList,數(shù)組存放的是Integer類型的值。

new ArrayList();

那么底層做了什么操作呢?

1.2、ArrayList的底層原理

1.2.1 初始化數(shù)組

/**
?*?Constructs?an?empty?list?with?an?initial?capacity?of?ten.
?*/ public ArrayList() { this.elementData?=?DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

創(chuàng)建了一個空數(shù)組,容量為0,根據(jù)官方的英文注釋,這里容量應(yīng)該為10,但其實是0,后續(xù)會講到為什么不是10。

1.2.1 ArrayList的add操作

public boolean add(E?e) {
????ensureCapacityInternal(size?+ 1); //?Increments?modCount!! elementData[size++]?=?e; return true;
}

重點是這一步:elementData[size++] = e; size++和elementData[xx]=e,這兩個操作都不是原子操作(不可分割的一個或一系列操作,要么都成功執(zhí)行,要么都不執(zhí)行)。

1.2.2 ArrayList擴容源碼解析

(1)執(zhí)行add操作時,會先確認是否超過數(shù)組大小

ensureCapacityInternal(size?+ 1);

(2)計算數(shù)組的當前容量calculateCapacity

private void ensureCapacityInternal(int minCapacity) {
????ensureExplicitCapacity(calculateCapacity(elementData,?minCapacity));
}

minCapacity: 值為1

elementData:代表當前數(shù)組

我們先看ensureCapacityInternal調(diào)用的ensureCapacityInternal方法

calculateCapacity(elementData,?minCapacity)

calculateCapacity方法如下:

private static int calculateCapacity(Object[]?elementData, int minCapacity) { if (elementData?==?DEFAULTCAPACITY_EMPTY_ELEMENTDATA)?{ return Math.max(DEFAULT_CAPACITY,?minCapacity);
????} return minCapacity;
}

elementData:代表當前數(shù)組,添加第一個元素時,elementData等于DEFAULTCAPACITY_EMPTY_ELEMENTDATA(空數(shù)組)

minCapacity:等于1

DEFAULT_CAPACITY:等于10

返回 Math.max(DEFAULT_CAPACITY, minCapacity) = 10

小結(jié):所以第一次添加元素時,計算數(shù)組的大小為10

(3)確定當前容量ensureExplicitCapacity

minCapacity = 10

elementData.length=0

小結(jié):因minCapacity > elementData.length,所以進行第一次擴容,調(diào)用grow()方法從0擴大到10

(4)調(diào)用grow方法

oldCapacity=0,newCapacity=10。

然后執(zhí)行 elementData = Arrays.copyOf(elementData, newCapacity);

將當前數(shù)組和容量大小進行數(shù)組拷貝操作,賦值給elementData。數(shù)組的容量設(shè)置為10

elementData的值和DEFAULTCAPACITY_EMPTY_ELEMENTDATA的值將會不一樣。

(5)然后將元素賦值給數(shù)組第一個元素,且size自增1

elementData[size++]?=?e;

(6)添加第二個元素時,傳給ensureCapacityInternal的是2

ensureCapacityInternal(size?+?1)

size=1,size+1=2

(7)第二次添加元素時,執(zhí)行calculateCapacity

elementData的值和DEFAULTCAPACITY_EMPTY_ELEMENTDATA的值不相等,所以直接返回2

(8)第二次添加元素時,執(zhí)行ensureExplicitCapacity

因minCapacity等于2,小于當前數(shù)組的長度10,所以不進行擴容,不執(zhí)行g(shù)row方法。

(9)將第二個元素添加到數(shù)組中,size自增1

elementData[size++]?=?e

(10)當添加第11個元素時調(diào)用grow方法進行擴容

minCapacity=11, elementData.length=10,調(diào)用grow方法。

(11)擴容1.5倍

int newCapacity?=?oldCapacity?+?(oldCapacity?>> 1);

oldCapacity=10,先換算成二級制1010,然后右移一位,變成0101,對應(yīng)十進制5,所以newCapacity=10+5=15,擴容1.5倍后是15。

(12)小結(jié)

  • 1.ArrayList初始化為一個 空數(shù)組
  • 2.ArrayList的Add操作不是線程安全的
  • 3.ArrayList添加第一個元素時,數(shù)組的容量設(shè)置為 10
  • 4.當ArrayList數(shù)組超過當前容量時,擴容至 1.5倍(遇到計算結(jié)果為小數(shù)的,向下取整),第一次擴容后,容量為15,第二次擴容至22...
  • 5.ArrayList在第一次和擴容后都會對數(shù)組進行拷貝,調(diào)用 Arrays.copyOf方法。

1.3、ArrayList單線程環(huán)境是否安全?

場景:

我們通過一個添加積木的例子來說明單線程下ArrayList是線程安全的。

將 積木 三角形A、四邊形B、五邊形C、六邊形D、五角星E依次添加到一個盒子中,盒子中共有5個方格,每一個方格可以放一個積木。

代碼實現(xiàn):

(1)這次我們用新的積木類BuildingBlockWithName

這個積木類可以傳形狀shape和名字name

/**
?*?積木類
?* @author:?悟空聊架構(gòu)
?* @create:?2020-08-27
?*/ class BuildingBlockWithName {
????String?shape;
????String?name; public BuildingBlockWithName(String?shape,?String?name) { this.shape?=?shape; this.name?=?name;
????} @Override public String toString() { return "BuildingBlockWithName{" + "shape='" +?shape?+ ",name=" +?name?+'}';
????}
}

(2)初始化一個ArrayList

ArrayListarrayList?= new ArrayList<>();

(3)依次添加三角形A、四邊形B、五邊形C、六邊形D、五角星E

arrayList.add(new BuildingBlockWithName("三角形", "A"));
arrayList.add(new BuildingBlockWithName("四邊形", "B"));
arrayList.add(new BuildingBlockWithName("五邊形", "C"));
arrayList.add(new BuildingBlockWithName("六邊形", "D"));
arrayList.add(new BuildingBlockWithName("五角星", "E"));

(4)驗證arrayList中元素的內(nèi)容和順序是否和添加的一致

BuildingBlockWithName{shape='三角形,name=A}
BuildingBlockWithName{shape='四邊形,name=B}
BuildingBlockWithName{shape='五邊形,name=C}
BuildingBlockWithName{shape='六邊形,name=D}
BuildingBlockWithName{shape='五角星,name=E} 

我們看到結(jié)果確實是一致的。

小結(jié): 單線程環(huán)境中,ArrayList是線程安全的。

1.4、多線程下ArrayList是不安全的

場景如下: 20個線程隨機往ArrayList添加一個任意形狀的積木。

(1)代碼實現(xiàn):20個線程往數(shù)組中隨機存放一個積木。

(2)打印結(jié)果:程序開始運行后,每個線程只存放一個隨機的積木。

數(shù)組中會不斷存放積木,多個線程會爭搶數(shù)組的存放資格,在存放過程中,會拋出一個異常: ConcurrentModificationException(并行修改異常)

Exception?in?thread "10" Exception?in?thread "13" java.util.ConcurrentModificationException

這個就是常見的并發(fā)異常:java.util.ConcurrentModificationException

1.5 那如何解決ArrayList線程不安全問題呢?

有如下方案:

  • 1.用Vector代替ArrayList
  • 2.用Collections.synchronized(new ArrayList<>())
  • 3.CopyOnWriteArrayList

1.6 Vector是保證線程安全的?

下面就來分析vector的源碼。

1.6.1 初始化Vector

初始化容量為10

public Vector() { this(10);
}

1.6.2 Add操作是線程安全的

Add方法加了synchronized,來保證add操作是線程安全的(保證可見性、原子性、有序性),對這幾個概念有不懂的可以看下之前的寫的文章-》 反制面試官 | 14張原理圖 | 再也不怕被問 volatile!

1.6.3 Vector擴容至2倍

int?newCapacity?=?oldCapacity?+?((capacityIncrement?>?0)???capacityIncrement?:?oldCapacity);

注意: capacityIncrement 在初始化的時候可以傳值,不傳則默認為0。如果傳了,則第一次擴容時為設(shè)置的oldCapacity+capacityIncrement,第二次擴容時擴大1倍。

缺點: 雖然保證了線程安全,但因為加了排斥鎖synchronized,會造成阻塞,所以性能降低。

1.6.4 用積木模擬Vector的add操作

當往vector存放元素時,給盒子加了一個鎖,只有一個人可以存放積木,放完后,釋放鎖,放第二元素時,再進行加鎖,依次往復(fù)進行。

1.7 使用Collections.synchronizedList保證線程安全

我們可以使用Collections.synchronizedList方法來封裝一個ArrayList。

ListarrayList?=?Collections.synchronizedList(new?ArrayList<>());

為什么這樣封裝后,就是線程安全的?

源碼解析: 因為Collections.synchronizedList封裝后的list,list的所有操作方法都是帶synchronized關(guān)鍵字的(除iterator()之外),相當于所有操作都會進行加鎖,所以使用它是線程安全的(除迭代數(shù)組之外)。

注意: 當?shù)鷶?shù)組時,需要手動做同步。官方示例如下:

synchronized (list)?{
?????Iterator?i?=?list.iterator(); //?Must?be?in?synchronized?block while (i.hasNext())
?????????foo(i.next());
}

1.8 使用CopyOnWriteArrayList保證線程安全

1.8.1 CopyOnWriteArrayList思想

  • Copy on write:寫時復(fù)制,一種讀寫分離的思想。
  • 寫操作:添加元素時,不直接往當前容器添加,而是先拷貝一份數(shù)組,在新的數(shù)組中添加元素后,在將原容器的引用指向新的容器。因為數(shù)組時用volatile關(guān)鍵字修飾的,所以當array重新賦值后,其他線程可以立即知道(volatile的可見性)
  • 讀操作:讀取數(shù)組時,讀老的數(shù)組,不需要加鎖。
  • 讀寫分離:寫操作是copy了一份新的數(shù)組進行寫,讀操作是讀老的數(shù)組,所以是讀寫分離。

1.8.2 使用方式

CopyOnWriteArrayListarrayList?= new CopyOnWriteArrayList<>();

1.8.3 底層源碼分析

add的流程:

  • 先定義了一個可重入鎖 ReentrantLock
  • 添加元素前,先獲取鎖 lock.lock()
  • 添加元素時,先拷貝當前數(shù)組 Arrays.copyOf
  • 添加元素時,擴容+1( len + 1)
  • 添加元素后,將數(shù)組引用指向新加了元素后的數(shù)組 setArray(newElements)

為什么數(shù)組重新賦值后,其他線程可以立即知道?

因為這里的數(shù)組是用volatile修飾的,哇,又是volatile,這個關(guān)鍵字真妙^_^

private transient volatile Object[]?array;

1.8.4 ReentrantLock 和synchronized的區(qū)別

劃重點

相同點:

  • 1.都是用來協(xié)調(diào)多線程對共享對象、變量的訪問
  • 2.都是可重入鎖,同一線程可以多次獲得同一個鎖
  • 3.都保證了可見性和互斥性

不同點:

  • 1.ReentrantLock 顯示的獲得、釋放鎖, synchronized 隱式獲得釋放鎖
  • 2.ReentrantLock 可響應(yīng)中斷, synchronized 是不可以響應(yīng)中斷的,為處理鎖的不可用性提供了更高的靈活性
  • 3.ReentrantLock 是 API 級別的, synchronized 是 JVM 級別的
  • 4.ReentrantLock 可以實現(xiàn)公平鎖、非公平鎖
  • 5.ReentrantLock 通過 Condition 可以綁定多個條件
  • 6.底層實現(xiàn)不一樣, synchronized 是同步阻塞,使用的是悲觀并發(fā)策略, lock 是同步非阻塞,采用的是樂觀并發(fā)策略

1.8.5 Lock和synchronized的區(qū)別

  • 1.Lock需要手動獲取鎖和釋放鎖。就好比自動擋和手動擋的區(qū)別
  • 1.Lock 是一個接口,而 synchronized 是 Java 中的關(guān)鍵字, synchronized 是內(nèi)置的語言實現(xiàn)。
  • 2.synchronized 在發(fā)生異常時,會自動釋放線程占有的鎖,因此不會導致死鎖現(xiàn)象發(fā)生;而 Lock 在發(fā)生異常時,如果沒有主動通過 unLock()去釋放鎖,則很可能造成死鎖現(xiàn)象,因此使用 Lock 時需要在 finally 塊中釋放鎖。
  • 3.Lock 可以讓等待鎖的線程響應(yīng)中斷,而 synchronized 卻不行,使用 synchronized 時,等待的線程會一直等待下去,不能夠響應(yīng)中斷。
  • 4.通過 Lock 可以知道有沒有成功獲取鎖,而 synchronized 卻無法辦到。
  • 5.Lock 可以通過實現(xiàn)讀寫鎖提高多個線程進行讀操作的效率。

二、線程不安全之HashSet

有了前面大篇幅的講解ArrayList的線程不安全,以及如何使用其他方式來保證線程安全,現(xiàn)在講HashSet應(yīng)該更容易理解一些。

2.1 HashSet的用法

用法如下:

SetSet?= new HashSet<>();
set.add("a");

初始容量=10,負載因子=0.75(當元素個數(shù)達到容量的75%,啟動擴容)

2.2 HashSet的底層原理

public HashSet()?{
????map?=?new?HashMap<>();
}

底層用的還是HashMap()。

考點: 為什么HashSet的add操作只用傳一個參數(shù)(value),而HashMap需要傳兩個參數(shù)(key和value)

2.3 HashSet的add操作

private static final Object?PRESENT?= new Object(); public boolean add(E?e) { return map.put(e,?PRESENT)==null;
}

考點回答: 因為HashSet的add操作中,key等于傳的value值,而value是PRESENT,PRESENT是new Object();,所以傳給map的是 key=e, ?value=new Object。Hash只關(guān)心key,不考慮value。

為什么HashSet不安全: 底層add操作不保證可見性、原子性。所以不是線程安全的。

2.4 如何保證線程安全

  • 1.使用Collections.synchronizedSet

    Setset?=?Collections.synchronizedSet(new HashSet<>());
  • 2.使用CopyOnWriteArraySet

    CopyOnWriteArraySetset =?new?CopyOnWriteArraySet<>();

2.5 CopyOnWriteArraySet的底層還是使用的是CopyOnWriteArrayList

public CopyOnWriteArraySet() {
????al?= new CopyOnWriteArrayList();
}

三、線程不安全之HashMap

3.1 HashMap的使用

同理,HashMap和HashSet一樣,在多線程環(huán)境下也是線程不安全的。

Map?map?= new HashMap<>();
map.put("A", new BuildingBlockWithName("三角形", "A"));

3.2 HashMap線程不安全解決方案:

  • 1.Collections.synchronizedMap
Map?map2?=?Collections.synchronizedMap(new?HashMap<>());
  • 2.ConcurrentHashMap
ConcurrentHashMap?set3?=?new?ConcurrentHashMap<>();

3.3 ConcurrentHashMap原理

ConcurrentHashMap,它內(nèi)部細分了若干個小的 HashMap,稱之為段(Segment)。默認情況下一個 ConcurrentHashMap 被進一步細分為 16 個段,既就是鎖的并發(fā)度。如果需要在 ConcurrentHashMap 中添加一個新的表項,并不是將整個 HashMap 加鎖,而是首先根據(jù) hashcode 得到該表項應(yīng)該存放在哪個段中,然后對該段加鎖,并完成 put 操作。在多線程環(huán)境中,如果多個線程同時進行put操作,只要被加入的表項不存放在同一個段中,則線程間可以做到真正的并行。

四、其他的集合類

LinkedList: 線程不安全,同ArrayListTreeSet: 線程不安全,同HashSetLinkedHashSet: 線程不安全,同HashSetTreeMap: 同HashMap,線程不安全HashTable: 線程安全

總結(jié)

本篇第一個部分詳細講述了ArrayList集合的底層擴容原理,演示了ArrayList的線程不安全會導致拋出并發(fā)修改異常。然后通過源碼解析的方式講解了三種方式來保證線程安全:

  • Vector是通過在 add等方法前加 synchronized來保證線程安全
  • Collections.synchronized()是通過包裝數(shù)組,在數(shù)組的操作方法前加 synchronized來保證線程安全
  • CopyOnWriteArrayList通過 寫時復(fù)制來保證線程安全的。

第二部分講解了HashSet的線程不安全性,通過兩種方式保證線程安全:

  • Collections.synchronizedSet
  • CopyOnWriteArraySet

第三部分講解了HashMap的線程不安全性,通過兩種方式保證線程安全:

  • Collections.synchronizedMap
  • ConcurrentHashMap

另外在講解的過程中,也詳細對比了ReentrantLock和synchronized及Lock和synchronized的區(qū)別。

特別推薦一個分享架構(gòu)+算法的優(yōu)質(zhì)內(nèi)容,還沒關(guān)注的小伙伴,可以長按關(guān)注一下:

全網(wǎng)最細 | 一文帶你領(lǐng)略集合的線程不安全

全網(wǎng)最細 | 一文帶你領(lǐng)略集合的線程不安全

全網(wǎng)最細 | 一文帶你領(lǐng)略集合的線程不安全

長按訂閱更多精彩▼

全網(wǎng)最細 | 一文帶你領(lǐng)略集合的線程不安全

如有收獲,點個在看,誠摯感謝

免責聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺僅提供信息存儲服務(wù)。文章僅代表作者個人觀點,不代表本平臺立場,如有問題,請聯(lián)系我們,謝謝!

本站聲明: 本文章由作者或相關(guān)機構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點,本站亦不保證或承諾內(nèi)容真實性等。需要轉(zhuǎn)載請聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請及時聯(lián)系本站刪除。
換一批
延伸閱讀

LED驅(qū)動電源的輸入包括高壓工頻交流(即市電)、低壓直流、高壓直流、低壓高頻交流(如電子變壓器的輸出)等。

關(guān)鍵字: 驅(qū)動電源

在工業(yè)自動化蓬勃發(fā)展的當下,工業(yè)電機作為核心動力設(shè)備,其驅(qū)動電源的性能直接關(guān)系到整個系統(tǒng)的穩(wěn)定性和可靠性。其中,反電動勢抑制與過流保護是驅(qū)動電源設(shè)計中至關(guān)重要的兩個環(huán)節(jié),集成化方案的設(shè)計成為提升電機驅(qū)動性能的關(guān)鍵。

關(guān)鍵字: 工業(yè)電機 驅(qū)動電源

LED 驅(qū)動電源作為 LED 照明系統(tǒng)的 “心臟”,其穩(wěn)定性直接決定了整個照明設(shè)備的使用壽命。然而,在實際應(yīng)用中,LED 驅(qū)動電源易損壞的問題卻十分常見,不僅增加了維護成本,還影響了用戶體驗。要解決這一問題,需從設(shè)計、生...

關(guān)鍵字: 驅(qū)動電源 照明系統(tǒng) 散熱

根據(jù)LED驅(qū)動電源的公式,電感內(nèi)電流波動大小和電感值成反比,輸出紋波和輸出電容值成反比。所以加大電感值和輸出電容值可以減小紋波。

關(guān)鍵字: LED 設(shè)計 驅(qū)動電源

電動汽車(EV)作為新能源汽車的重要代表,正逐漸成為全球汽車產(chǎn)業(yè)的重要發(fā)展方向。電動汽車的核心技術(shù)之一是電機驅(qū)動控制系統(tǒng),而絕緣柵雙極型晶體管(IGBT)作為電機驅(qū)動系統(tǒng)中的關(guān)鍵元件,其性能直接影響到電動汽車的動力性能和...

關(guān)鍵字: 電動汽車 新能源 驅(qū)動電源

在現(xiàn)代城市建設(shè)中,街道及停車場照明作為基礎(chǔ)設(shè)施的重要組成部分,其質(zhì)量和效率直接關(guān)系到城市的公共安全、居民生活質(zhì)量和能源利用效率。隨著科技的進步,高亮度白光發(fā)光二極管(LED)因其獨特的優(yōu)勢逐漸取代傳統(tǒng)光源,成為大功率區(qū)域...

關(guān)鍵字: 發(fā)光二極管 驅(qū)動電源 LED

LED通用照明設(shè)計工程師會遇到許多挑戰(zhàn),如功率密度、功率因數(shù)校正(PFC)、空間受限和可靠性等。

關(guān)鍵字: LED 驅(qū)動電源 功率因數(shù)校正

在LED照明技術(shù)日益普及的今天,LED驅(qū)動電源的電磁干擾(EMI)問題成為了一個不可忽視的挑戰(zhàn)。電磁干擾不僅會影響LED燈具的正常工作,還可能對周圍電子設(shè)備造成不利影響,甚至引發(fā)系統(tǒng)故障。因此,采取有效的硬件措施來解決L...

關(guān)鍵字: LED照明技術(shù) 電磁干擾 驅(qū)動電源

開關(guān)電源具有效率高的特性,而且開關(guān)電源的變壓器體積比串聯(lián)穩(wěn)壓型電源的要小得多,電源電路比較整潔,整機重量也有所下降,所以,現(xiàn)在的LED驅(qū)動電源

關(guān)鍵字: LED 驅(qū)動電源 開關(guān)電源

LED驅(qū)動電源是把電源供應(yīng)轉(zhuǎn)換為特定的電壓電流以驅(qū)動LED發(fā)光的電壓轉(zhuǎn)換器,通常情況下:LED驅(qū)動電源的輸入包括高壓工頻交流(即市電)、低壓直流、高壓直流、低壓高頻交流(如電子變壓器的輸出)等。

關(guān)鍵字: LED 隧道燈 驅(qū)動電源
關(guān)閉