進(jìn)程和線程區(qū)別?線程安全和非線程安全區(qū)別?
進(jìn)程與線程
進(jìn)程是具有一定獨(dú)立功能的程序關(guān)于某個(gè)數(shù)據(jù)集合上的一次運(yùn)行活動(dòng),是系統(tǒng)進(jìn)行資源分配和調(diào)度的一個(gè)獨(dú)立單位。每個(gè)進(jìn)程都有自己的獨(dú)立內(nèi)存空間,不同進(jìn)程通過進(jìn)程間通信來通信。由于進(jìn)程比較重量,占據(jù)獨(dú)立的內(nèi)存,所以上下文進(jìn)程間的切換開銷(棧、寄存器、虛擬內(nèi)存、文件句柄等)比較大,但相對比較穩(wěn)定安全。線程是進(jìn)程的一個(gè)實(shí)體,是CPU調(diào)度和分派的基本單位,它是比進(jìn)程更小的能獨(dú)立運(yùn)行的基本單位.線程自己基本上不擁有系統(tǒng)資源,只擁有一點(diǎn)在運(yùn)行中必不可少的資源(如程序計(jì)數(shù)器,一組寄存器和棧),但是它可與同屬一個(gè)進(jìn)程的其他的線程共享進(jìn)程所擁有的全部資源。線程間通信主要通過共享內(nèi)存,上下文切換很快,資源開銷較少,但相比進(jìn)程不夠穩(wěn)定容易丟失數(shù)據(jù)。
區(qū)別
①進(jìn)程是資源分配最小單位,線程是CPU調(diào)度最小單位;
②每個(gè)進(jìn)程都有獨(dú)立的代碼和數(shù)據(jù)空間(程序上下文),程序之間的切換會(huì)有較大的開銷;線程可以看做輕量級的進(jìn)程,同一類線程共享代碼和數(shù)據(jù)空間,每個(gè)線程都有自己獨(dú)立的運(yùn)行棧和程序計(jì)數(shù)器(PC),線程之間切換的開銷小。
④線程之間通信更方便,同一個(gè)進(jìn)程下,線程共享全局變量,靜態(tài)變量等數(shù)據(jù),進(jìn)程之間的通信需要以通信的方式(IPC)進(jìn)行;(但多線程程序處理好同步與互斥是個(gè)難點(diǎn))
⑤多進(jìn)程程序更安全,生命力更強(qiáng),一個(gè)進(jìn)程死掉不會(huì)對另一個(gè)進(jìn)程造成影響(源于有獨(dú)立的地址空間),多線程程序更不易維護(hù),一個(gè)線程死掉,整個(gè)進(jìn)程就死掉了(因?yàn)楣蚕淼刂房臻g)
線程安全:線程不安全指的是,在堆內(nèi)存中的數(shù)據(jù)由于可以被任何線程訪問到,在沒有限制的情況下存在被意外修改的風(fēng)險(xiǎn)。即堆內(nèi)存空間在沒有保護(hù)機(jī)制的情況下,對多線程來說是不安全的地方,因?yàn)槟惴胚M(jìn)去的數(shù)據(jù),可能被別的線程“破壞”。
線程狀態(tài),start,run,wait,notify,yiled,sleep,join等方法的作用以及區(qū)別
線程狀態(tài):

start:啟動(dòng)一個(gè)線程并使線程進(jìn)入了就緒狀態(tài),當(dāng)分配到時(shí)間片后就可以開始運(yùn)行
run:run()方法稱為線程體,直接執(zhí)行 run() 方法,會(huì)把 run 方法當(dāng)成一個(gè) main 線程下的普通方法去執(zhí)行,并不會(huì)在某個(gè)線程中執(zhí)行它,所以這并不是多線程工作。如果調(diào)用start會(huì)自動(dòng)調(diào)用run
wait:使一個(gè)線程處于等待(阻塞)狀態(tài),并且釋放所持有的對象的鎖
sleep:使一個(gè)正在運(yùn)行的線程處于睡眠狀態(tài),是一個(gè)靜態(tài)方法,調(diào)用此方法要處理 InterruptedException 異常。
notify:喚醒一個(gè)處于等待狀態(tài)的線程,在調(diào)用此方法的時(shí)候,并不能確切的喚醒某一個(gè)等待狀態(tài)的線程,而是由 JVM 確定喚醒哪個(gè)線程,而且與優(yōu)先級無關(guān)。
notityAll:喚醒所有處于等待狀態(tài)的線程,該方法并不是將對象的鎖給所有線程,而是讓它們競爭,只有獲得鎖的線程才能進(jìn)入就緒狀態(tài);
yield:使當(dāng)前線程從執(zhí)行狀態(tài)(運(yùn)行狀態(tài))變?yōu)榭蓤?zhí)行態(tài)(就緒狀態(tài))。
join:等待調(diào)用該方法的線程結(jié)束后才能執(zhí)行,也就是調(diào)用該方法的進(jìn)程會(huì)搶占CPU優(yōu)先執(zhí)行,執(zhí)行完這個(gè)進(jìn)程其他進(jìn)程才能執(zhí)行。
sleep() 和 wait() 有什么區(qū)別?
兩者都可以暫停線程的執(zhí)行
類的不同:sleep() 是 Thread線程類的靜態(tài)方法,wait() 是 Object類的方法。
是否釋放鎖:sleep() 不釋放鎖;wait() 釋放鎖。
用途不同:Wait 通常被用于線程間交互/通信,sleep 通常被用于暫停執(zhí)行。
用法不同:wait() 方法被調(diào)用后,線程不會(huì)自動(dòng)蘇醒,需要?jiǎng)e的線程調(diào)用同一個(gè)對象上的 notify() 或者 notifyAll() 方法。sleep() 方法執(zhí)行完成后,線程會(huì)自動(dòng)蘇醒?;蛘呖梢允褂脀ait(long timeout)超時(shí)后線程會(huì)自動(dòng)蘇醒。
wait,notify阻塞喚醒確切過程?在哪阻塞,在哪喚醒?為什么要出現(xiàn)在同步代碼塊中,為什么要處于while循環(huán)中?被定義在 Object 類里?
執(zhí)行wait方法后,在其他線程調(diào)用 此對象的notify 方法或者 nofifyall方法前 導(dǎo)致當(dāng)前的線程處于等待的狀態(tài)。對于有參的函數(shù)來說 以上的兩條成立的情況下 還會(huì)在時(shí)間超時(shí)之前也是處于等待的狀態(tài)。在執(zhí)行完 wait方法以后。線程會(huì)釋放掉所占用的鎖標(biāo)識(shí) 從而使線程所在的對象中的其他synchronized數(shù)據(jù)可被別的線程使用。
在同步代碼塊中:當(dāng)一個(gè)線程需要調(diào)用對象的 wait()方法的時(shí)候,這個(gè)線程必須擁有該對象的鎖,接著它就會(huì)釋放這個(gè)對象鎖并進(jìn)入等待狀態(tài)直到其他線程調(diào)用這個(gè)對象上的 notify()方法。同樣的,當(dāng)一個(gè)線程需要調(diào)用對象的 notify()方法時(shí),它會(huì)釋放這個(gè)對象的鎖,以便其他在等待的線程就可以得到這個(gè)對象鎖。由于所有的這些方法都需要線程持有對象的鎖,這樣就只能通過同步來實(shí)現(xiàn),所以他們只能在同步方法或者同步塊中被調(diào)用。
while循環(huán)中:因?yàn)楫?dāng)線程獲取到 CPU 開始執(zhí)行的時(shí)候,其他條件可能還沒有滿足,所以在處理前,循環(huán)檢測條件是否滿足會(huì)更好。
定義在Object中:Java提供的鎖是對象級的而不是線程級的,每個(gè)對象都有個(gè)鎖,而線程是可以獲得這個(gè)對象的。因此線程需要等待某些鎖,那么只要調(diào)用對象中的wait()方法便可以了。而wait()方法如果定義在Thread類中的話,那么線程正在等待的是哪個(gè)鎖就不明確了。這也就是說wait,notify和notifyAll都是鎖級別的操作,所以把他們定義在Object類中是因?yàn)殒i是屬于對象的原因。
線程中斷,守護(hù)線程?
用戶線程和守護(hù)線程:
用戶 (User) 線程:運(yùn)行在前臺(tái),執(zhí)行具體的任務(wù),如程序的主線程、連接網(wǎng)絡(luò)的子線程等都是用戶線程
守護(hù) (Daemon) 線程:運(yùn)行在后臺(tái),為其他前臺(tái)線程服務(wù)。一旦所有用戶線程都結(jié)束運(yùn)行,守護(hù)線程會(huì)隨 JVM 一起結(jié)束工作,比如GC。
比較明顯的區(qū)別之一是用戶線程結(jié)束,JVM 退出,不管這個(gè)時(shí)候有沒有守護(hù)線程運(yùn)行。而守護(hù)線程不會(huì)影響 JVM 的退出。注意:
setDaemon(true)必須在start()方法前執(zhí)行,否則會(huì)拋出 IllegalThreadStateException 異常
在守護(hù)線程中產(chǎn)生的新線程也是守護(hù)線程
不是所有的任務(wù)都可以分配給守護(hù)線程來執(zhí)行
線程中斷:
Java沒有提供任何機(jī)制來安全地終止線程,但提供了中斷機(jī)制,即thread.interrupt()方法。線程中斷是一種協(xié)作式的機(jī)制,并不是說調(diào)用了中斷方法之后目標(biāo)線程一定會(huì)立即中斷,而是發(fā)送了一個(gè)中斷請求給目標(biāo)線程,目標(biāo)線程會(huì)自行在某個(gè)取消點(diǎn)中斷自己。涉及到中斷的線程基礎(chǔ)方法有三個(gè):interrupt()、isInterrupted()、interrupted(),它們都位于Thread類下。interrupt()方法:對目標(biāo)線程發(fā)送中斷請求,看其源碼會(huì)發(fā)現(xiàn)最終是調(diào)用了一個(gè)本地方法實(shí)現(xiàn)的線程中斷;interrupted()方法:返回目標(biāo)線程是否中斷的布爾值(通過本地方法實(shí)現(xiàn)),且返回后會(huì)重置中斷狀態(tài)為未中斷;isInterrupted()方法:該方法返回的是線程中斷與否的布爾值(通過本地方法實(shí)現(xiàn)),不會(huì)重置中斷狀態(tài);
synchronized使用方法?底層實(shí)現(xiàn)?
synchronized 關(guān)鍵字解決的是多個(gè)線程之間訪問資源的同步性,synchronized關(guān)鍵字可以保證被它修飾的方法或者代碼塊在任意時(shí)刻只能有一個(gè)線程執(zhí)行。
使用方式:
修飾實(shí)例方法,對當(dāng)前實(shí)例對象this加鎖
修飾靜態(tài)方法,對當(dāng)前類的Class對象加鎖
修飾代碼塊,指定一個(gè)加鎖的對象,給對象加鎖
即:synchronized 關(guān)鍵字加到 static 靜態(tài)方法和 synchronized(class)代碼塊上都是給 Class 類上鎖。synchronized 關(guān)鍵字加到實(shí)例方法上是給對象實(shí)例上鎖。
底層實(shí)現(xiàn):
Java 對象在內(nèi)存中的表示
對象頭:
Mark Word(標(biāo)記字段):默認(rèn)存儲(chǔ)對象的HashCode,分代年齡和鎖標(biāo)志位信息。它會(huì)根據(jù)對象的狀態(tài)復(fù)用自己的存儲(chǔ)空間,也就是說在運(yùn)行期間Mark Word里存儲(chǔ)的數(shù)據(jù)會(huì)隨著鎖標(biāo)志位的變化而變化。
Class Point(類型指針):對象指向它的類元數(shù)據(jù)的指針,虛擬機(jī)通過這個(gè)指針來確定這個(gè)對象是哪個(gè)類的實(shí)例。
實(shí)例數(shù)據(jù):
這部分主要是存放類的數(shù)據(jù)信息,父類的信息。
對齊填充:
由于虛擬機(jī)要求對象起始地址必須是8字節(jié)的整數(shù)倍,填充數(shù)據(jù)不是必須存在的,僅僅是為了字節(jié)對齊。
Monitor
每個(gè)對象都有一個(gè)與之關(guān)聯(lián)的Monitor 對象,monitor對象存儲(chǔ)著當(dāng)前持有鎖的線程以及等待鎖的線程隊(duì)列等。
synchronized 同步語句塊的實(shí)現(xiàn)使用的是 monitorenter 和 monitorexit 指令,其中 monitorenter 指令指向同步代碼塊的開始位置,monitorexit 指令則指明同步代碼塊的結(jié)束位置。當(dāng)執(zhí)行 monitorenter 指令時(shí),線程試圖獲取鎖也就是獲取 對象監(jiān)視器 monitor 的持有權(quán)。
JVM 通過該 ACC_SYNCHRONIZED 訪問標(biāo)志來辨別一個(gè)方法是否聲明為同步方法,隱式調(diào)用剛才的兩個(gè)指令:monitorenter和monitorexit。
重量鎖:加鎖過程為將MonitorObject 中的 _owner設(shè)置成 當(dāng)前線程; 設(shè)置 mark word的 Monitor 對象地址,更改鎖標(biāo)志位;將阻塞線程放到 ContentionList 隊(duì)列。加鎖依賴底層操作系統(tǒng)的 mutex 相關(guān)指令實(shí)現(xiàn),加鎖解鎖需要在用戶態(tài)和內(nèi)核態(tài)之間切換,性能損耗非常明顯。
鎖升級

偏向鎖:該鎖提出的原因是,開發(fā)者發(fā)現(xiàn)多數(shù)情況下鎖并不存在競爭,一把鎖往往是由同一個(gè)線程獲得的。偏向鎖的申請流程:
1.首先需要判斷對象的 Mark Word 是否屬于偏向模式,如果不屬于,那就進(jìn)入輕量級鎖判斷邏輯。否則繼續(xù)下一步判斷;
2.判斷目前請求鎖的線程 ID 是否和偏向鎖本身記錄的線程 ID 一致。如果一致,則獲取鎖,如果不一致,執(zhí)行步驟3;
3.使用CAS 操作將 Thread ID 放到 Mark Word 當(dāng)中;
如果CAS 成功,此時(shí)線程A 就獲取了鎖,如果線程CAS 失敗,證明有別的線程持有鎖,例如上圖的線程B 來CAS 就失敗的,這個(gè)時(shí)候啟動(dòng)偏向鎖撤銷 (revoke bias),升級為輕量鎖。
4.鎖撤銷流程:-讓線程在全局安全點(diǎn)阻塞(類似于GC前線程在安全點(diǎn)阻塞) - 遍歷線程棧,查看是否有被鎖對象的鎖記錄( Lock Record),如果有Lock Record,需要修復(fù)鎖記錄和Markword,使其變成無鎖狀態(tài)。- 恢復(fù)A線程 - 將是否為偏向鎖狀態(tài)置為 0 ,開始進(jìn)行輕量級加鎖流程
輕量鎖:輕量級鎖的設(shè)計(jì)初衷在于并發(fā)程序開發(fā)者的經(jīng)驗(yàn)“對于絕大部分的鎖,在整個(gè)同步周期內(nèi)都是不存在競爭的”。加鎖過程:
1.如果當(dāng)前這個(gè)對象的鎖標(biāo)志位為 01(即無鎖狀態(tài)或者偏向鎖鎖狀態(tài)),線程在執(zhí)行同步塊之前,JVM 會(huì)先在當(dāng)前的線程的棧幀中創(chuàng)建一個(gè) Lock Record,包括一個(gè)用于存儲(chǔ)對象頭中的 Mark Word 以及一個(gè)指向加鎖對象的指針。
2.利用 CAS 算法對這個(gè)對象的 Mark Word 指向棧中鎖記錄的指針進(jìn)行修改。如果修改成功,那該線程就擁有了這個(gè)對象的鎖。如果失敗,則自旋,嘗試到一定次數(shù)(默認(rèn)10次)依然沒有拿到,鎖就會(huì)升級成重量級鎖。
Java樂觀鎖機(jī)制,CAS思想?缺點(diǎn)?是否原子性?如何保證?
樂觀鎖:樂觀鎖做事比較樂觀,它假定沖突的概率很低,它的工作方式是:先修改完共享資源,再驗(yàn)證這段時(shí)間內(nèi)有沒有發(fā)生沖突,如果沒有其他線程在修改資源,那么操作完成,如果發(fā)現(xiàn)有其他線程已經(jīng)修改過這個(gè)資源,就放棄本次操作。
CAS:線程在讀取數(shù)據(jù)時(shí)不進(jìn)行加鎖,讀取值為A,計(jì)算要新寫入的值V,在準(zhǔn)備寫回?cái)?shù)據(jù)時(shí),先去查最新內(nèi)存值B,比較A與B是否相等,即原值是否被修改,若未被其他線程修改則寫回,若已被修改,則自旋重試。
缺點(diǎn):
ABA問題:比如說一個(gè)線程 one 從內(nèi)存位置 V 中取出 A,這時(shí)候另一個(gè)線程 two 也從內(nèi)存中取出 A,并且 two 進(jìn)行了一些操作變成了 B,然后 two 又將 V 位置的數(shù)據(jù)變成 A,這時(shí)候線程 one 進(jìn)行 CAS 操作發(fā)現(xiàn)內(nèi)存中仍然是 A,然后 one 操作成功。盡管線程 one 的 CAS 操作成功,但可能存在潛藏的問題。
循環(huán)時(shí)間長開銷大:對于資源競爭嚴(yán)重(線程沖突嚴(yán)重)的情況,CAS操作長時(shí)間不成功的話,會(huì)導(dǎo)致一直自旋,相當(dāng)于死循環(huán)了,CPU的壓力會(huì)很大。
只能保證一個(gè)共享變量的原子操作:當(dāng)對一個(gè)共享變量執(zhí)行操作時(shí),我們可以使用循環(huán) CAS 的方式來保證原子操作,但是對多個(gè)共享變量操作時(shí),循環(huán) CAS 就無法保證操作的原子性。此時(shí)考慮使用JUC原子類。
volatile作用?底層實(shí)現(xiàn)?禁止重排序的場景?單例模式volatile的作用?
volatile:
volatile關(guān)鍵詞的作用一般有如下兩個(gè):
可見性:當(dāng)一個(gè)線程修改了由volatile關(guān)鍵字修飾的變量的值時(shí),其它線程能夠立即得知這個(gè)修改。
有序性:禁止編譯器關(guān)于操作volatile關(guān)鍵詞修飾的變量的指令重排序。
但是volatile 不保證操作的原子性
他的使用場景比如:
修飾標(biāo)志位和double-check 的懶漢單例模式中的實(shí)例變量
底層實(shí)現(xiàn):
內(nèi)存模型:Java虛擬機(jī)規(guī)范中定義了一種Java內(nèi)存 模型(Java Memory Model,即JMM)來屏蔽掉各種硬件和操作系統(tǒng)的內(nèi)存訪問差異,以實(shí)現(xiàn)讓Java程序在各種平臺(tái)下都能達(dá)到一致的運(yùn)行效果。Java內(nèi)存模型的主要目標(biāo)就是定義程序中各個(gè)變量的訪問規(guī)則,即在虛擬機(jī)中將變量存儲(chǔ)到內(nèi)存和從內(nèi)存中取出變量這樣的細(xì)節(jié)。JMM中規(guī)定所有的變量都存儲(chǔ)在主內(nèi)存(Main Memory)中,每條線程都有自己的工作內(nèi)存(Work Memory,本地工作內(nèi)存是一個(gè)抽象概念,包括了緩存、store buffer、寄存器等),線程的工作內(nèi)存中保存了該線程所使用的變量的從主內(nèi)存中拷貝的副本。線程對于變量的讀、寫都必須在工作內(nèi)存中進(jìn)行,而不能直接讀、寫主內(nèi)存中的變量。同時(shí),本線程的工作內(nèi)存的變量也無法被其他線程直接訪問, 線程間通信必須要經(jīng)過主內(nèi)存。Java內(nèi)存模型定義了八種操作,來實(shí)現(xiàn)一個(gè)變量從主內(nèi)存拷貝到工作內(nèi)存、從工作內(nèi)存同步到主內(nèi)存之間的實(shí)現(xiàn)細(xì)節(jié):lock unlock read load use assign store write。
緩存一致性:當(dāng)多個(gè)處理器的運(yùn)算任務(wù)都涉及同一塊主內(nèi)存區(qū)域時(shí),將可能導(dǎo)致各自的緩存數(shù)據(jù)不一致,為了解決一致性的問題,需要各個(gè)處理器訪問緩存時(shí)都遵循一些協(xié)議,在讀寫時(shí)要根據(jù)協(xié)議來進(jìn)行操作,這類協(xié)議有MSI、MESI等。MESI主要思想是當(dāng)CPU寫數(shù)據(jù)時(shí),如果發(fā)現(xiàn)操作的變量是共享變量,即在其他CPU中也存在該變量的副本,會(huì)發(fā)出信號通知其他CPU將該變量的緩存行置為無效狀態(tài),因此當(dāng)其他CPU需要讀取這個(gè)變量時(shí),發(fā)現(xiàn)自己緩存中緩存該變量的緩存行是無效的,那么它就會(huì)從內(nèi)存重新讀取。每個(gè)處理器通過嗅探在總線上傳播的數(shù)據(jù)來檢查自己緩存的值是不是過期了,當(dāng)處理器發(fā)現(xiàn)自己緩存行對應(yīng)的內(nèi)存地址被修改,就會(huì)將當(dāng)前處理器的緩存行設(shè)置成無效狀態(tài)。
happens-before原則保障可見性,禁止指令重排保證有序性。如果一個(gè)操作執(zhí)行的結(jié)果需要對另一個(gè)操作可見,那么這兩個(gè)操作之間必須存在happens-before關(guān)系。
volatile規(guī)則:對一個(gè)volatile的寫操作,happens-before于任意線程后續(xù)對這個(gè)volatile的讀。Java編譯器在生成指令序列的適當(dāng)位置會(huì)插入內(nèi)存屏障指令來禁止特定類型的處理器重排序,保證共享變量操作的有序性。
內(nèi)存屏障指令:禁止volatile 修飾變量指令的重排序
寫入數(shù)據(jù)強(qiáng)制刷新到主存
讀取數(shù)據(jù)強(qiáng)制從主存讀取

synchronized和volatile區(qū)別:
volatile 是變量修飾符;synchronized 可以修飾類、方法、變量。
volatile 僅能實(shí)現(xiàn)變量的修改可見性和有序性,不能保證原子性;而 synchronized 則可以保證變量的修改可見性和原子性和有序性。
volatile 不會(huì)造成線程的阻塞;synchronized 可能會(huì)造成線程的阻塞。
volatile標(biāo)記的變量不會(huì)被編譯器優(yōu)化;synchronized的變量可以被編譯器優(yōu)化。
鎖優(yōu)化。自旋鎖、自適應(yīng)自旋鎖、鎖消除、鎖粗化、偏向鎖、輕量級鎖、重量級鎖解釋
自旋鎖:通過讓線程執(zhí)行一個(gè)忙循環(huán)(自旋)等待鎖的釋放,不讓出CPU
自適應(yīng)自旋鎖:自旋的時(shí)間不再固定了,而是由前一次在同一個(gè)鎖上的自旋時(shí)間及鎖的擁有者的狀態(tài)來決定
鎖消除:鎖消除是指虛擬機(jī)即時(shí)編譯器在運(yùn)行時(shí),對一些代碼上要求同步,但是被檢測到不可能存在共享數(shù)據(jù)競爭的鎖進(jìn)行消除。
鎖粗化:如果虛擬機(jī)探測到有一串零碎的操作都對同一個(gè)對象加鎖,將會(huì)把加鎖同步的范圍擴(kuò)展 (粗化)到整個(gè)操作序列的外部
公平鎖和非公平鎖區(qū)別?為什么公平鎖效率低?
公平鎖:多個(gè)線程按照申請鎖的順序去獲得鎖,線程會(huì)直接進(jìn)入隊(duì)列去排隊(duì),永遠(yuǎn)都是隊(duì)列的第一位才能得到鎖。優(yōu)點(diǎn):所有的線程都能得到資源,不會(huì)餓死在隊(duì)列中。缺點(diǎn):吞吐量會(huì)下降很多,隊(duì)列里面除了第一個(gè)線程,其他的線程都會(huì)阻塞,cpu喚醒阻塞線程的開銷會(huì)很大。
非公平鎖:多個(gè)線程去獲取鎖的時(shí)候,會(huì)直接去嘗試獲取,獲取不到,再去進(jìn)入等待隊(duì)列,如果能獲取到,就直接獲取到鎖。優(yōu)點(diǎn):可以減少CPU喚醒線程的開銷,整體的吞吐效率會(huì)高點(diǎn),CPU也不必取喚醒所有線程,會(huì)減少喚起線程的數(shù)量。缺點(diǎn):你們可能也發(fā)現(xiàn)了,這樣可能導(dǎo)致隊(duì)列中間的線程一直獲取不到鎖或者長時(shí)間獲取不到鎖,導(dǎo)致餓死。
ThreadLocal原理,如何使用?
ThreadLocal的作用主要是做數(shù)據(jù)隔離,如果創(chuàng)建了一個(gè)ThreadLocal變量,那么訪問這個(gè)變量的每個(gè)線程都會(huì)有這個(gè)變量的本地副本,它對別的線程而言是相對隔離的,在多線程環(huán)境下,防止自己的變量被其它線程篡改??梢允褂?get和 set 方法來獲取默認(rèn)值或?qū)⑵渲蹈臑楫?dāng)前線程所存的副本的值,從而避免了線程安全問題。InheritableThreadLocal可以實(shí)現(xiàn)多個(gè)線程訪問ThreadLocal的值
使用——Spring 事務(wù)的實(shí)現(xiàn):事務(wù)需要保證一個(gè)事務(wù)的所有操作都在同一個(gè)數(shù)據(jù)庫連接上,Spring就是利用ThreadLocal來實(shí)現(xiàn)這一點(diǎn)的。ThreadLocal存儲(chǔ)的類型是map,key是data source, value是connection,ThreadLoacl保證了同一個(gè)線程獲取一個(gè)Connection對象,從而保證同一個(gè)事務(wù)的操作都在一個(gè)數(shù)據(jù)庫連接上。
原理:每個(gè)線程Thread都維護(hù)了自己的threadLocals 變量,所以在每個(gè)線程創(chuàng)建ThreadLocal的時(shí)候,實(shí)際上數(shù)據(jù)是存在自己線程Thread的threadLocals 變量里面的,別人沒辦法拿到,從而實(shí)現(xiàn)了隔離,這個(gè)變量即是ThreadLocalMap 類,是一個(gè)類似 Map 的數(shù)據(jù)結(jié)構(gòu),他的Entry是繼承WeakReference(弱引用)的,key 為當(dāng)前對象的 Thread 對象,值為 Object 對象。ThreadLocalMap在存儲(chǔ)的時(shí)候會(huì)給每一個(gè)ThreadLocal對象一個(gè)threadLocalHashCode,在插入過程中,根據(jù)ThreadLocal對象的hash值,定位到table中的位置i,int i = key.threadLocalHashCode & (len-1)。
內(nèi)存泄露:ThreadLocalMap 中使用的 key 為 ThreadLocal 的弱引用,而 value 是強(qiáng)引用。所以,如果 ThreadLocal 沒有被外部強(qiáng)引用的情況下,在垃圾回收的時(shí)候,key 會(huì)被清理掉,而 value 不會(huì)被清理掉。這樣一來,ThreadLocalMap 中就會(huì)出現(xiàn) key 為 null 的 Entry。假如我們不做任何措施的話,value 永遠(yuǎn)無法被 GC 回收,這個(gè)時(shí)候就可能會(huì)產(chǎn)生內(nèi)存泄露。ThreadLocalMap 實(shí)現(xiàn)中已經(jīng)考慮了這種情況,在調(diào)用 set()、get()、remove() 方法的時(shí)候,會(huì)清理掉 key 為 null 的記錄。使用完 ThreadLocal方法后 最好手動(dòng)調(diào)用remove()方法

AQS思想
AQS 的全稱為(AbstractQueuedSynchronizer),這個(gè)類在java.util.concurrent.locks包下面,AQS 是一個(gè)用來構(gòu)建鎖和同步器的框架,使用 AQS 能簡單且高效地構(gòu)造出應(yīng)用廣泛的大量的同步器。比如 ReentrantLock,Semaphore, ReentrantReadWriteLock。AQS 核心思想是,使用一個(gè) int 成員變量來表示同步狀態(tài),state 由于是多線程共享變量,所以必須定義成 volatile,以保證 state 的可見性, 同時(shí)雖然 volatile 能保證可見性,但不能保證原子性,所以 AQS 提供了對 state 的原子操作方法,保證了線程安全(getState,setState,compareAndSetState 進(jìn)行操作)。在多線程條件下,線程要執(zhí)行臨界區(qū)的代碼,必須首先獲取 state,某個(gè)線程獲取成功之后, state 加 1,其他線程再獲取的話由于共享資源已被占用,所以會(huì)到 FIFO 等待隊(duì)列去等待,等占有 state 的線程執(zhí)行完臨界區(qū)的代碼釋放資源( state 減 1)后,會(huì)喚醒 FIFO 中的下一個(gè)等待線程(head 中的下一個(gè)結(jié)點(diǎn))去獲取 state。另外 AQS 中實(shí)現(xiàn)的 FIFO 隊(duì)列(CLH 隊(duì)列)其實(shí)是雙向鏈表實(shí)現(xiàn)的,由 head, tail 節(jié)點(diǎn)表示,head 結(jié)點(diǎn)代表當(dāng)前占用的線程,其他節(jié)點(diǎn)由于暫時(shí)獲取不到鎖所以依次排隊(duì)等待鎖釋放。

ReenTrantLock使用方法?底層實(shí)現(xiàn)?和synchronized區(qū)別?
使用方法:
// 1. 初始化可重入鎖
private ReentrantLock lock = new ReentrantLock();
public void run() {
// 加鎖
lock.lock();
try {
// 2. 執(zhí)行臨界區(qū)代碼
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 3. 解鎖
lock.unlock();
}
}
底層實(shí)現(xiàn):
1)使用 CAS 來獲取 state 資源,如果成功設(shè)置 1,代表 state 資源獲取鎖成功,可以執(zhí)行同步代碼。
2)如果 CAS 設(shè)置 state 為 1 失?。ù慝@取鎖失敗),則執(zhí)行 acquire(1) 方法,這個(gè)方法是 AQS 提供的方法。
3)acquire首先會(huì)調(diào)用子類(ReentrantLock)的tryAcquire,tryAcquire會(huì)判斷state是否為0,如果為0,則說明沒有線程占有,繼續(xù)嘗試CAS獲取,如果獲取成功,則可執(zhí)行同步代碼。如果 state 不為 0,代表之前已有線程占有了鎖,如果此時(shí)的線程依然是之前占有鎖的線程(current == getExclusiveOwnerThread() 為 true),代表此線程再一次占有了鎖(可重入鎖),此時(shí)更新 state,記錄下鎖被占有的次數(shù)(鎖的重入次數(shù))。
4)如果 tryAcquire() 執(zhí)行失敗,代表獲取鎖失敗,執(zhí)行 addWaiter ,線程入FIFO隊(duì)列,然后執(zhí)行 acquireQueued,判斷前驅(qū)節(jié)點(diǎn)是不是head,如果是則CAS嘗試獲取,如果獲取成功則將當(dāng)前節(jié)點(diǎn)設(shè)為head,并置空前驅(qū)結(jié)點(diǎn)。如果沒獲取成功,則判斷前驅(qū)是不是SIGNAL,如果不是則找到和法的前驅(qū)結(jié)點(diǎn),并設(shè)置為SIGNAL。最后調(diào)用park將線程阻塞。

synchronized和reentrentlock區(qū)別
synchronized是 JVM 關(guān)鍵字,ReentrantLock 是JDK實(shí)現(xiàn)的類
ReentrantLock 必須手動(dòng)獲取與釋放鎖, synchronized 不需要手動(dòng)釋放和開啟鎖;
ReentrantLock 只適用于代碼塊鎖,而 synchronized 可以修飾類、方法、變量等。
二者的鎖機(jī)制其實(shí)也是不一樣的。ReentrantLock 底層調(diào)用的是 Unsafe 的park 方法加鎖,synchronized 操作的應(yīng)該是對象頭中 mark word。
CountDownLatch、CyclicBarrier、Semaphore介紹
CountDownLatch:它是一個(gè)同步輔助器,允許一個(gè)或多個(gè)線程一直等待,直到一組在其他線程執(zhí)行的操作全部完成。它的構(gòu)造方法,會(huì)傳入一個(gè) count 值,用于計(jì)數(shù)。當(dāng)一個(gè)線程調(diào)用await方法時(shí),就會(huì)阻塞當(dāng)前線程。每當(dāng)有線程調(diào)用一次 countDown 方法時(shí),計(jì)數(shù)就會(huì)減 1。當(dāng) count 的值等于 0 的時(shí)候,被阻塞的線程才會(huì)繼續(xù)運(yùn)行。
CyclicBarrier:用來控制多個(gè)線程互相等待,只有當(dāng)多個(gè)線程都到達(dá)時(shí),這些線程才會(huì)繼續(xù)執(zhí)行。和 CountdownLatch 相似,都是通過維護(hù)計(jì)數(shù)器來實(shí)現(xiàn)的。線程執(zhí)行 await() 方法之后計(jì)數(shù)器會(huì)減 1,并進(jìn)行等待,直到計(jì)數(shù)器為 0,所有調(diào)用 await() 方法而在等待的線程才能繼續(xù)執(zhí)行。
CyclicBarrier 和 CountdownLatch 的一個(gè)區(qū)別是,CyclicBarrier 的計(jì)數(shù)器通過調(diào)用 reset() 方法可以循環(huán)使用,所以它才叫做循環(huán)屏障。還有就是,一等多,和多個(gè)互相等待的區(qū)別。
Semaphore: 用來控制同一時(shí)間,資源可被訪問的線程數(shù)量,一般可用于流量的控制。