2. JUC
總結(jié)不易,希望大家給點(diǎn)支持,堅(jiān)持每日一更,如果需要更加全面的資料可以聯(lián)系wx:qh950520
java 并發(fā)編程并發(fā):多個(gè)線程去訪問(wèn)同一個(gè)資源并行:各種事情同時(shí)去做,一邊干什么,一邊干什么
1. Synchronized
1.sync的使用
可以用用于代碼塊,普通方法,靜態(tài)方法
代碼塊----對(duì)象鎖,普通方法---對(duì)象鎖,靜態(tài)方法----類鎖
2.sync的主要作用
主要使用來(lái)解決多線程同步問(wèn)題的,其可以保證正在被修改的代碼任意時(shí)刻都只有一個(gè)線程執(zhí)行
3.sync的底層原理
被synchronized修飾的代碼,在被編譯器編譯后在被修飾的代碼前后加上了一組字節(jié)指令。
在代碼開始加入了monitorenter,在代碼后面加入了monitorexit,這兩個(gè)字節(jié)碼指令配合完成了synchronized關(guān)鍵字修飾代碼的互斥訪問(wèn)
在虛擬機(jī)執(zhí)行到monitorenter指令的時(shí)候,會(huì)請(qǐng)求獲取對(duì)象的monitor鎖,基于monitor鎖又衍生出一個(gè)鎖計(jì)數(shù)器的概念
當(dāng)執(zhí)行monitorenter時(shí),若對(duì)象未被鎖定時(shí),或者當(dāng)前線程已經(jīng)擁有了此對(duì)象的monitor鎖,則鎖計(jì)數(shù)器+1,該線程獲取該對(duì)象鎖。
當(dāng)執(zhí)行monitorexit時(shí),鎖計(jì)數(shù)器-1,當(dāng)計(jì)數(shù)器為0時(shí),此對(duì)象鎖就被釋放了。那么其他阻塞的線程則可以請(qǐng)求獲取該monitor鎖。
拓展:sync修飾普通方法底層原理:
普通方法,其常量池中多了ACC_SYNCHRONIZED標(biāo)示符。JVM就是根據(jù)該標(biāo)示符來(lái)實(shí)現(xiàn)方法的同步的:當(dāng)方法調(diào)用時(shí),調(diào)用指令將會(huì)檢查方法的 ACC_SYNCHRONIZED 訪問(wèn)標(biāo)志是否被設(shè)置,如果設(shè)置了,執(zhí)行線程將先獲取monitor,獲取成功之后才能執(zhí)行方法體,方法執(zhí)行完后再釋放monitor。在方法執(zhí)行期間,其他任何線程都無(wú)法再獲得同一個(gè)monitor對(duì)象。 其實(shí)本質(zhì)上沒(méi)有區(qū)別,只是方法的同步是一種隱式的方式來(lái)實(shí)現(xiàn),無(wú)需通過(guò)字節(jié)碼來(lái)完成。
面試:Synchronized的可重入怎么實(shí)現(xiàn)的?
重入鎖實(shí)現(xiàn)可重入性原理或機(jī)制是:每一個(gè)鎖關(guān)聯(lián)一個(gè)線程持有者和計(jì)數(shù)器,當(dāng)計(jì)數(shù)器為 0 時(shí)表示該鎖沒(méi)有被任何線程持有,那么任何線程都可能獲得該鎖而調(diào)用相應(yīng)的方法;當(dāng)某一線程請(qǐng)求成功后,JVM會(huì)記下鎖的持有線程,并且將計(jì)數(shù)器置為 1;此時(shí)其它線程請(qǐng)求該鎖,則必須等待;而該持有鎖的線程如果再次請(qǐng)求這個(gè)鎖,就可以再次拿到這個(gè)鎖,同時(shí)計(jì)數(shù)器會(huì)遞增;當(dāng)線程退出同步代碼塊時(shí),計(jì)數(shù)器會(huì)遞減,如果計(jì)數(shù)器為 0,則釋放該鎖。
synchronized 方法若發(fā)生異常,則JVM會(huì)自動(dòng)釋放鎖。
鎖對(duì)象不能為空,否則拋出NPE(NullPointerException)
繼承創(chuàng)建了父類對(duì)象,并把父類對(duì)象的引用交給了子類,但是在super.去調(diào)用方法的時(shí)候JVM認(rèn)為調(diào)用者依然是子類。所以子類從寫父類sync方法,鎖對(duì)象都是子類。
2. volatile
volatile關(guān)鍵字
對(duì)volatile的理解:volatile是java虛擬機(jī)提供的輕量級(jí)的同步機(jī)制,主要兩大特性:
保證可見性,禁止指令重排序但不保證原子性
為什么不保證原子性?
java只對(duì)基本數(shù)據(jù)類型變量的賦值和讀取是原子操作,想 i++這種是非原子操作,
1、讀內(nèi)存到寄存器;2、在寄存器中自增;3、寫回內(nèi)存。如果被volatile修飾了,肯定能保證每次讀取這個(gè)變量都是最新的值,但一旦需要對(duì)這個(gè)變量進(jìn)行自增這樣的非原子操作,就無(wú)法保證原子性了。
怎么保證可見性?
對(duì)volatile修飾的變量,執(zhí)行寫操作的話,JVM會(huì)發(fā)送一條lock前綴指令給CPU,CPU在計(jì)算完之后會(huì)立即將這個(gè)值寫回主內(nèi)存,同時(shí)因?yàn)橛?b>MESI緩存一致性協(xié)議,所以各個(gè)CPU都會(huì)對(duì)總線進(jìn)行嗅探,自己本地緩存中的數(shù)據(jù)是否被別人修改
如果發(fā)現(xiàn)別人修改了某個(gè)緩存的數(shù)據(jù),那么CPU就會(huì)將自己本地緩存的數(shù)據(jù)過(guò)期,然后這個(gè)CPU上執(zhí)行的線程在讀取那個(gè)變量的時(shí)候,就會(huì)從主內(nèi)存重新加載最新的數(shù)據(jù)。
lock前綴指令 + MESI緩存一致性協(xié)議
volatile為什么可以禁止指令重排序?
對(duì)于volatile修改變量的讀寫操作,都會(huì)加入內(nèi)存屏障
每個(gè)volatile寫操作前面,加StoreStore屏障,禁止上面的普通寫和他重排;每個(gè)volatile寫操作后面,加StoreLoad屏障,禁止跟下面的volatile讀/寫重排
每個(gè)volatile讀操作后面,加LoadLoad屏障,禁止下面的普通讀和voaltile讀重排;每個(gè)volatile讀操作后面,加LoadStore屏障,禁止下面的普通寫和volatile讀重排
sync怎么保證有序性?
synchronized則是由“一個(gè)變量在同一時(shí)刻只允許一條線程對(duì)其進(jìn)行l(wèi)ock操作”這條規(guī)則獲取的。
3.Lock/ReenTrantLock/ReadWriteLock
synchronized和lock的區(qū)別?
sync是個(gè)java關(guān)鍵字,lock是個(gè)java類
sync無(wú)法判斷獲取鎖的狀態(tài),lock可以判斷是否獲取鎖
sync會(huì)自動(dòng)釋放鎖,lock需要手動(dòng)釋放,否則發(fā)生死鎖
線程獲取sync鎖后,另外一個(gè)線程會(huì)一直等待,而lock就不一定會(huì)等待(可中斷)
適合鎖少量同步代碼塊,Lock適合鎖大量同步代碼塊
java中有哪些鎖?
公平鎖/非公平鎖
公平鎖:多個(gè)線程按照順序獲取鎖
非公平鎖:線程獲取鎖的順序并不是按照申請(qǐng)鎖的順序,有可能后申請(qǐng)的線程比先申請(qǐng)的線程優(yōu)先獲取鎖
可重入鎖
可重入鎖又名遞歸鎖,是指在同一個(gè)線程在外層方法獲取鎖的時(shí)候,在進(jìn)入內(nèi)層方法會(huì)自動(dòng)獲取鎖。 sync和reentrantlock
獨(dú)享鎖/共享鎖
獨(dú)享鎖是指該鎖一次只能被一個(gè)線程所持有,ReentrantLock,sync,writeLock
共享鎖是指該鎖可被多個(gè)線程所持有,readLock
互斥鎖/讀寫鎖
互斥鎖在Java中的具體實(shí)現(xiàn)就是ReentrantLock
讀寫鎖在Java中的具體實(shí)現(xiàn)就是ReadWriteLock
樂(lè)觀鎖/悲觀鎖
樂(lè)觀鎖則認(rèn)為對(duì)于同一個(gè)數(shù)據(jù)的并發(fā)操作,是不會(huì)發(fā)生修改的。在更新數(shù)據(jù)的時(shí)候,會(huì)采用嘗試更新,不斷重新的方式更新數(shù)據(jù)
悲觀鎖認(rèn)為對(duì)于同一個(gè)數(shù)據(jù)的并發(fā)操作,一定是會(huì)發(fā)生修改的,哪怕沒(méi)有修改,也會(huì)認(rèn)為修改
樂(lè)觀鎖可以使用volatile+CAS原語(yǔ)實(shí)現(xiàn),帶參數(shù)版本來(lái)避免ABA問(wèn)題,在讀取和替換的時(shí)候進(jìn)行判定版本是否一致
悲觀鎖可以使用synchronize的以及Lock
分段鎖
分段鎖其實(shí)是一種鎖的設(shè)計(jì),并不是具體的一種鎖,對(duì)于ConcurrentHashMap而言,其并發(fā)的實(shí)現(xiàn)就是通過(guò)分段鎖的形式來(lái)實(shí)現(xiàn)高效的并發(fā)操作。
偏向鎖/輕量級(jí)鎖/重量級(jí)鎖
Synchronized
偏向鎖是指一段同步代碼一直被一個(gè)線程所訪問(wèn),那么該線程會(huì)自動(dòng)獲取鎖。降低獲取鎖的代價(jià)。
輕量級(jí)鎖是指當(dāng)鎖是偏向鎖的時(shí)候,被另一個(gè)線程所訪問(wèn),偏向鎖就會(huì)升級(jí)為輕量級(jí)鎖,其他線程會(huì)通過(guò)自旋的形式嘗試獲取鎖,不會(huì)阻塞,提高性能。
重量級(jí)鎖是指當(dāng)鎖為輕量級(jí)鎖的時(shí)候,另一個(gè)線程雖然是自旋,但自旋不會(huì)一直持續(xù)下去,當(dāng)自旋一定次數(shù)的時(shí)候,還沒(méi)有獲取到鎖,就會(huì)進(jìn)入阻塞,該鎖膨脹為重量級(jí)鎖。重量級(jí)鎖會(huì)讓其他申請(qǐng)的線程進(jìn)入阻塞,性能降低。
自旋鎖
在Java中,自旋鎖是指嘗試獲取鎖的線程不會(huì)立即阻塞,而是采用循環(huán)的方式去嘗試獲取鎖,這樣的好處是減少線程上下文切換的消耗,缺點(diǎn)是循環(huán)會(huì)消耗CPU。
原理:
第一個(gè)線程: 第一次來(lái)獲取鎖,立刻獲取到鎖
第二個(gè)線程: 線程一還沒(méi)有處理完,就要加入到隊(duì)列,并關(guān)心它的前驅(qū)節(jié)點(diǎn)鎖狀態(tài),然后進(jìn)行自旋。
第三步: 釋放鎖, 線程1把自身的locked = false ,同時(shí)把當(dāng)前節(jié)點(diǎn)改為了前節(jié)點(diǎn)。
死鎖是什么?
當(dāng)兩個(gè)線程循環(huán)依賴于一對(duì)同步對(duì)象(monitor)時(shí)將發(fā)生死鎖
比如線程1的占用了D資源,線程2的占用了C資源 ,此時(shí)線程1需要資源C,線程2需要D資源,他們就一直等待對(duì)方釋放資源,導(dǎo)致死鎖。
死鎖的4個(gè)必要條件?
當(dāng)且僅當(dāng)以下所有條件同時(shí)存在于系統(tǒng)中時(shí),才會(huì)出現(xiàn)資源上的死鎖情況:
相互排斥:系統(tǒng)中必須有非共享的資源,即在任何給定的時(shí)刻,只有一個(gè)進(jìn)程可以使用該資源。
保持等待:進(jìn)程當(dāng)前持有至少一個(gè)資源并請(qǐng)求其他進(jìn)程持有的其他資源。
不可搶占:資源只能由持有它的進(jìn)程自愿釋放,其他進(jìn)程不可強(qiáng)行占有該資源。
循環(huán)等待:每個(gè)進(jìn)程必須等待另一個(gè)進(jìn)程持有的資源,而該進(jìn)程又等待第一個(gè)進(jìn)程釋放資源
鎖優(yōu)化?
一般并發(fā)用到鎖就是阻塞的,因此鎖優(yōu)化就是在堵塞的情況下去提高性能
? 持有時(shí)間減少和鎖的范圍 (比如以前是對(duì)整個(gè)方法加鎖,現(xiàn)在只對(duì)方法內(nèi)代碼塊加鎖)
? 減小鎖粒度(高性能的hash表,他就是做了減少鎖粒度的實(shí)現(xiàn),他被拆分好像16個(gè)Segment,每個(gè)Segment就是一個(gè)個(gè)小的hashmap.。就是把大的hash表拆成若干個(gè)小的hash表。)在減小鎖粒度后, ConcurrentHashMap允許若干個(gè)線程同時(shí)進(jìn)入
? 鎖分離(分離,就是讀寫鎖分離,讀不用改變數(shù)據(jù),所以所有的讀不會(huì)產(chǎn)生堵塞,讀不會(huì)產(chǎn)生堵塞。當(dāng)寫的時(shí)候才去進(jìn)行堵塞,對(duì)于讀多寫少的場(chǎng)景,應(yīng)用比較好)
? 鎖粗化(對(duì)同一個(gè)鎖不停的進(jìn)行請(qǐng)求、同步和釋放,其本身也會(huì)消耗系統(tǒng)寶貴的資源,反而不利于性能的優(yōu)化。因此可以把很多次請(qǐng)求的鎖拿到一個(gè)鎖里面,但前提是:中間不需要的同步的代碼塊很很快的執(zhí)行完。
例如:for(in i=0;i<count;i++){
Synchronized()
}
改為
Synchronized(){
for(in i=0;i<count;i++){}
}
)
? 鎖消除
在即時(shí)編譯器時(shí),如果發(fā)現(xiàn)不可能被共享的對(duì)象,則可以消除這些對(duì)象的鎖操作。
比如StringBuffer ,他本來(lái)就是線程安全的,他內(nèi)部有很多sycn修飾
ConCurrentHashMap分段鎖的概念
ConcurrentHashMap中的分段鎖稱為Segment,它即類似于HashMap(JDK7與JDK8中HashMap的實(shí)現(xiàn))的結(jié)構(gòu),即內(nèi)部擁有一個(gè)Entry數(shù)組,數(shù)組中的每個(gè)元素又是一個(gè)鏈表;同時(shí)又是一個(gè)ReentrantLock(Segment繼承了ReentrantLock)。
當(dāng)需要put元素的時(shí)候,并不是對(duì)整個(gè)hashmap進(jìn)行加鎖,而是先通過(guò)hashcode來(lái)知道他要放在那一個(gè)分段中,然后對(duì)這個(gè)分段進(jìn)行加鎖,所以當(dāng)多線程put的時(shí)候,只要不是放在一個(gè)分段中,就實(shí)現(xiàn)了真正的并行的插入。
但是,在統(tǒng)計(jì)size的時(shí)候,可就是獲取hashmap全局信息的時(shí)候,就需要獲取所有的分段鎖才能統(tǒng)計(jì)。
分段鎖的設(shè)計(jì)目的是細(xì)化鎖的粒度,當(dāng)操作不需要更新整個(gè)數(shù)組的時(shí)候,就僅僅針對(duì)數(shù)組中的一項(xiàng)進(jìn)行加鎖操作。
我們面對(duì)ReentrantLock和synchronized改如何選擇?
Synchronized相比Lock,為許多開發(fā)人員所熟悉,并且簡(jiǎn)潔緊湊,如果現(xiàn)有程序已經(jīng)使用了內(nèi)置鎖,那么盡量保持代碼風(fēng)格統(tǒng)一,盡量不引入Lock,避免兩種機(jī)制混用,容易令人困惑,也容易發(fā)生錯(cuò)誤。在Synchronized無(wú)法滿足需求的情況下,Lock可以作為一種高級(jí)工具,這些功能包括“可定時(shí)的、可輪詢的與可中斷的鎖獲取操作,公平隊(duì)列,以及非塊結(jié)構(gòu)的鎖”否則還是優(yōu)先使用Synchronized。最后,未來(lái)更可能提升Synchronized而不是Lock的性能,因?yàn)?b>Synchronized是JVM的內(nèi)置屬性,他能執(zhí)行一些優(yōu)化,例如對(duì)線程封閉的鎖對(duì)象的鎖消除優(yōu)化,通過(guò)增加鎖的粒度來(lái)消除內(nèi)置鎖的同步,而如果基于類庫(kù)的鎖來(lái)實(shí)現(xiàn)這些功能,則可能性不大
4.CAS
原理:比較當(dāng)前工作內(nèi)存中的值和主內(nèi)存中的值,如果這個(gè)值是期望的,那么則執(zhí)行操作。如果不是就一直循環(huán)。
缺點(diǎn):
底層是自旋鎖,耗時(shí)
一次性只能保存一個(gè)共享變量的原子性
ABA問(wèn)題
ABA問(wèn)題:需要取出內(nèi)存中某時(shí)刻的數(shù)據(jù),而在下時(shí)刻比較并替換,那么在這個(gè)時(shí)間差類會(huì)導(dǎo)致數(shù)據(jù)的變化。
典型例子: 現(xiàn)有一個(gè)用單向鏈表實(shí)現(xiàn)的棧,棧頂為A,這時(shí)線程T1已經(jīng)知道A.next為B,然后希望用CAS將棧頂替換為B:head.compareAndSet(A,B);在T1執(zhí)行上面這條指令之前,線程T2介入,將A、B出棧,再pushD、C、A。此時(shí)輪到線程T1執(zhí)行CAS操作,檢測(cè)發(fā)現(xiàn)棧頂仍為A,所以CAS成功,棧頂變?yōu)锽,但實(shí)際上B.next為null,這樣CD就丟失了。
樂(lè)觀鎖的實(shí)現(xiàn)中通常都會(huì)用版本戳version來(lái)對(duì)記錄或?qū)ο髽?biāo)記,避免并發(fā)操作帶來(lái)的問(wèn)題,在Java中,AtomicStampedReference<E>也實(shí)現(xiàn)了這個(gè)作用
AtomicStampedReference(intialRef, initialStamp)
比較initalStamp,然后進(jìn)行compareAndSet
Unsafe 類
//java無(wú)法直接操作內(nèi)存,java可以調(diào)用C++,native方法,C++可以操作內(nèi)存
java通過(guò)Unsafe類可以操作native方法,來(lái)操作內(nèi)存
5.并發(fā)集合類
并發(fā)包下list集合CopyOnWriteArrayList
1.Vector? 已經(jīng)廢棄了,效率太低
2.List<String> list = Collections.synchronizedList(new ArrayList<>());? //效率低
3.List<String> list = new CopyOnWriteArrayList<>(new ArrayList<>());? JUC里面的方法,add加lock鎖CopyOnWrite 寫入時(shí)復(fù)制 COW 計(jì)算機(jī)領(lǐng)域的一種策略:多個(gè)線程調(diào)用的時(shí)候,List讀取的時(shí)候固定不變,寫入的時(shí)候存在覆蓋問(wèn)題。
在寫入的時(shí)候避免覆蓋創(chuàng)造的數(shù)據(jù)問(wèn)題,復(fù)制一份給調(diào)用者,調(diào)用完畢返回。
并發(fā)包下set集合CopyOnWriteArraySet
1.List<String> list = Collections.synchronizedSet(new ArraySet<>());? //效率低
2.List<String> list = new CopyOnWriteArraySet<>(new ArraySet<>());? JUC里面的方法,add加lock鎖CopyOnWrite 寫入時(shí)復(fù)制 COW 計(jì)算機(jī)領(lǐng)域的一種策略:
Queue
拋出異常是:NoSuchElementException

6.并發(fā)的三個(gè)輔助類
CountDownLatch 減法計(jì)數(shù)器,當(dāng)計(jì)數(shù)器為0時(shí)才向下執(zhí)行,否則await一直阻塞


CycliBarrier 加法計(jì)數(shù)器,達(dá)到指定記數(shù)值就會(huì)觸發(fā)下面流程
public class CyclicBarrierTest{
publicstaticvoidmain(String[]args)throwsBrokenBarrierException,InterruptedException{
/*
CyclicBarrier 構(gòu)造器有兩個(gè)參數(shù)
1. int? 要計(jì)數(shù)到的值
2. Runnable 達(dá)到計(jì)數(shù)器之后要執(zhí)行的線程
*/
CyclicBarriercyclicBarrier=newCyclicBarrier(7,()->{
System.out.println("召喚神龍成功");
? ? ?? });
?
for(inti=1;i<=7;i++) {
finalinttemp=i;
// Lambda 能操作 i 嗎
newThread(()->{
System.out.println(Thread.currentThread().getName()+"收集了:"+temp+"顆龍珠");
try{
cyclicBarrier.await();
}catch(InterruptedExceptione) {
e.printStackTrace();
}catch(BrokenBarrierExceptione) {
e.printStackTrace();
? ? ? ? ? ? ?? }
},String.valueOf(i)).start();
? ? ?? }
?? }
}
Semaphore sqmaphore.acquire() 假如線程已經(jīng)滿了,就等待有空為止.semaphore.release,

7.讀寫鎖

8.JMM
9.AQS
AbstractQueuedSynchronizer抽象隊(duì)列同步器簡(jiǎn)稱AQS
ReentrantLock,Semaphore,ReentrantReadWriteLock,SynchronousQueue,F(xiàn)utureTask等等皆是基于AQS的。
ReentrantLcok 底層加鎖原理
ReentrantLcok 內(nèi)有個(gè)AQS對(duì)象,這個(gè)AQS中含有一個(gè)核心變量state,是int類型的,代表加鎖狀態(tài),初始狀態(tài)是0,另外AQS還有一個(gè)關(guān)鍵變量,用來(lái)標(biāo)記當(dāng)前加鎖的是哪個(gè)線程,初始為null。
當(dāng)一個(gè)線程調(diào)用lock(), 直接利用CAS將state從0變?yōu)?,1表示加鎖成功,然后再設(shè)置當(dāng)前加鎖線程就是自己。
ReenTrantLock 互斥實(shí)現(xiàn)原理:
線程2跑過(guò)來(lái)一下看到,state的值不是0啊?所以CAS操作將state從0變?yōu)?的過(guò)程會(huì)失敗,因?yàn)閟tate的值當(dāng)前為1,說(shuō)明已經(jīng)有人加鎖了!
線程2會(huì)將自己放入AQS中的一個(gè)等待隊(duì)列,因?yàn)樽约簢L試加鎖失敗了,此時(shí)就要將自己放入隊(duì)列中來(lái)等待,等待線程1釋放鎖之后,自己就可以重新嘗試加鎖了
AQS就是一個(gè)并發(fā)包的基礎(chǔ)組件,用來(lái)實(shí)現(xiàn)各種鎖,各種同步組件的。
它包含了state變量、加鎖線程、等待隊(duì)列等并發(fā)中的核心組件。