多線程
1、線程是程序運(yùn)行的最小單位,是操作系統(tǒng)調(diào)度的最小單位。
2、一個(gè)進(jìn)程包含多個(gè)線程,多個(gè)線程共享所屬進(jìn)程的資源(CPU、IO等)和內(nèi)存空間,但是每個(gè)線程有自己獨(dú)立的??臻g。
3、使用多線程可以提高CPU利用率,提高系統(tǒng)響應(yīng)速度。
4、降低資源消耗。通過(guò)重復(fù)利用已創(chuàng)建的線程降低線程創(chuàng)建和銷毀造成的消耗。
5、提高響應(yīng)速度。當(dāng)任務(wù)到達(dá)時(shí),可以不需要等到線程創(chuàng)建就能立即執(zhí)行。
6、提高線程的可管理性。統(tǒng)一管理線程,避免系統(tǒng)創(chuàng)建大量同類線程而導(dǎo)致消耗完內(nèi)存。
進(jìn)程和線程的區(qū)別
1、系統(tǒng)會(huì)為進(jìn)程分配地址空間,不會(huì)為線程分配地址空間,但是線程有自己的堆棧和局部變量表。
2、系統(tǒng)會(huì)為進(jìn)程分配內(nèi)存,不會(huì)為線程分配內(nèi)存。
3、進(jìn)程和線程切換時(shí)都會(huì)切換上下文,但是進(jìn)程切換比線程切換更耗時(shí),更耗資源。
4、線程相對(duì)進(jìn)程并發(fā)性較高。
5、進(jìn)程有自己的程序運(yùn)行入口可以獨(dú)立運(yùn)行,線程得依賴程序的調(diào)度運(yùn)行。
6、在保護(hù)模式下進(jìn)程崩潰之后不會(huì)對(duì)其它進(jìn)程產(chǎn)生影響,但是線程崩潰了所屬的進(jìn)程也會(huì)崩潰掉。
線程創(chuàng)建方式
1、繼承Thread類。
2、實(shí)現(xiàn)Runnable接口。
3、實(shí)現(xiàn)Callable接口(Future、FutureTask配合可以用來(lái)獲取異步執(zhí)行的結(jié)果, Callable 接口 call 方法允許拋出異常,可以獲取異常信息 注:Callalbe接口支持返回執(zhí)行結(jié)果,需要調(diào)用FutureTask.get()得到)。
線程狀態(tài)
線程有五種狀態(tài):1.準(zhǔn)備,新建線程,沒有執(zhí)行start方法。2.就緒,執(zhí)行start方法,初始化線程,準(zhǔn)備時(shí)間片。3.運(yùn)行,執(zhí)行run方法。4.阻塞。(造成線程阻塞原因:sleep,wait,使用阻塞IO,同步鎖,suspend懸掛)5.銷毀。
線程通信
主要包含兩種方式Monitor和Condition
1、notify/notifyAll
2、signal/signalAll
線程停止
1、使用退出標(biāo)志,使線程正常退出,也就是當(dāng)run方法完成后線程終止。
2、使用stop方法強(qiáng)行終止,但是不推薦這個(gè)方法,因?yàn)閟top和suspend及resume一樣都是過(guò)期作廢的方法。
3、使用interrupt方法中斷線程。調(diào)用interrupt方法,可以通過(guò)isInterrupt方法判斷線程終止。
線程共享數(shù)據(jù)
1、如果多個(gè)線程執(zhí)行同一個(gè)Runnable實(shí)現(xiàn)類中的代碼,此時(shí)共享的數(shù)據(jù)放在Runnable實(shí)現(xiàn)類中;
2、如果多個(gè)線程執(zhí)行不同的Runnable實(shí)現(xiàn)類中的代碼,此時(shí)共享數(shù)據(jù)和操作共享數(shù)據(jù)的方法封裝到一個(gè)對(duì)象中,在不同的Runnable實(shí)現(xiàn)類中調(diào)用操作共享數(shù)據(jù)的方法。
線程異常
如果異常沒有被捕獲該線程將會(huì)停止執(zhí)行。Thread.UncaughtExceptionHandler是用于處理未捕獲異常造成線程突然中斷情況的一個(gè)內(nèi)嵌接口。當(dāng)一個(gè)未捕獲異常將造成線程中斷的時(shí)候,JVM 會(huì)使用 Thread.getUncaughtExceptionHandler()來(lái)查詢線程的 UncaughtExceptionHandler 并將線程和異常作為參數(shù)傳遞給 handler 的 uncaughtException()方法進(jìn)行處理。
線程順序執(zhí)行
假設(shè)有T1、T2、T3三個(gè)線程,你怎樣保證T2在T1執(zhí)行完后執(zhí)行,T3在T2執(zhí)行完后執(zhí)行?可以使用join方法解決這個(gè)問(wèn)題。比如在線程A中,調(diào)用線程B的join方法表示的意思就是:A等待B線程執(zhí)行完畢后(釋放CPU執(zhí)行權(quán)),在繼續(xù)執(zhí)行。
start()、run() 方法
1、new 一個(gè) Thread,線程進(jìn)入了新建狀態(tài)。調(diào)用 start() 方法,會(huì)啟動(dòng)一個(gè)線程并使線程進(jìn)入了就緒狀態(tài),當(dāng)分配到時(shí)間片后就可以開始運(yùn)行了。 start() 會(huì)執(zhí)行線程的相應(yīng)準(zhǔn)備工作,然后自動(dòng)執(zhí)行 run() 方法的內(nèi)容,這是真正的多線程工作。
2、而直接執(zhí)行 run() 方法,會(huì)把 run 方法當(dāng)成一個(gè) main 線程下的普通方法去執(zhí)行,并不會(huì)在某個(gè)線程中執(zhí)行它,所以這并不是多線程工作。
sleep()、 yield()方法
(1) sleep()方法給其他線程運(yùn)行機(jī)會(huì)時(shí)不考慮線程的優(yōu)先級(jí),因此會(huì)給低優(yōu)先級(jí)的線程以運(yùn)行的機(jī)會(huì);yield()方法只會(huì)給相同優(yōu)先級(jí)或更高優(yōu)先級(jí)的線程以運(yùn)行的機(jī)會(huì);
(2) 線程執(zhí)行 sleep()方法后轉(zhuǎn)入阻塞(blocked)狀態(tài),而執(zhí)行 yield()方法后轉(zhuǎn)入就緒(ready)狀態(tài);
(3)sleep()方法聲明拋出 InterruptedException,而 yield()方法沒有聲明任何異常;
(4)sleep()方法比 yield()方法(跟操作系統(tǒng) CPU 調(diào)度相關(guān))具有更好的可移植性,通常不建議使用yield()方法來(lái)控制并發(fā)線程的執(zhí)行。
sleep和sleep(0)的區(qū)別
1、當(dāng) timeout = 0, 即 Sleep(0),如果線程調(diào)度器的可運(yùn)行隊(duì)列中有大于或等于當(dāng)前線程優(yōu)先級(jí)的就緒線程存在,操作系統(tǒng)會(huì)將當(dāng)前線程從處理器上移除,調(diào)度其他優(yōu)先級(jí)高的就緒線程運(yùn)行;如果可運(yùn)行隊(duì)列中的沒有就緒線程或所有就緒線程的優(yōu)先級(jí)均低于當(dāng)前線程優(yōu)先級(jí),那么當(dāng)前線程會(huì)繼續(xù)執(zhí)行,就像沒有調(diào)用 Sleep(0)一樣。
2、當(dāng) timeout > 0 時(shí),如:Sleep(1),會(huì)引發(fā)線程上下文切換:調(diào)用線程會(huì)從線程調(diào)度器的可運(yùn)行隊(duì)列中被移除一段時(shí)間,這個(gè)時(shí)間段約等于 timeout 所指定的時(shí)間長(zhǎng)度。
線程等待、阻塞
線程等待是主動(dòng)釋放CPU資源,等待某個(gè)條件滿足后再繼續(xù)執(zhí)行,而線程阻塞是被動(dòng)等待某個(gè)條件的滿足,期間會(huì)一直占用CPU資源。
線程調(diào)度
1、分時(shí)調(diào)度:是指讓所有的線程輪流獲得 cpu 的使用權(quán),并且平均分配每個(gè)線程占用的 CPU 的時(shí)間片這個(gè)也比較好理解。
2、搶占式調(diào)度:Java虛擬機(jī)采用搶占式調(diào)度模型,是指優(yōu)先讓可運(yùn)行池中優(yōu)先級(jí)高的線程占用CPU,如果可運(yùn)行池中的線程優(yōu)先級(jí)相同,那么就隨機(jī)選擇一個(gè)線程,使其占用CPU。處于運(yùn)行狀態(tài)的線程會(huì)一直運(yùn)行,直至它不得不放棄 CPU。
JMM內(nèi)存模型
每個(gè)線程運(yùn)行時(shí),都會(huì)創(chuàng)建一個(gè)工作內(nèi)存(也叫??臻g),來(lái)保存線程所有的私有變量。而JMM內(nèi)存模型規(guī)范中規(guī)定所有的變量都存儲(chǔ)在主內(nèi)存中,而主內(nèi)存中的變量是所有的線程都可以共享的,而對(duì)主內(nèi)存中的變量進(jìn)行操作時(shí),必須在線程的工作內(nèi)存進(jìn)行操作,首先將主內(nèi)存的變量拷貝到工作內(nèi)存,進(jìn)行操作后,再將變量刷回到主內(nèi)存中。所有線程只有通過(guò)主內(nèi)存來(lái)進(jìn)行通信。
原子類
用過(guò)哪些原子類,他們的原理是什么?
1、Atomic基本原子類:AtomicInteger、AtomicLong、AtomicBoolean
2、AtomicArray數(shù)組類型原子類:AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray
3、AtomicReference引用類型原子類:AtomicReference、AtomicStampedReference、AtomicMarkableReference
4、AtomicFieldUpdater升級(jí)類型原子類:AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater
5、Adder累加器:LongAdder、DoubleAdder
原子類實(shí)現(xiàn)原理: 原子類是通過(guò)自旋CAS操作volatile變量實(shí)現(xiàn)的。
volatile
1、變量可見性:JMM會(huì)把該線程本地內(nèi)存中的變量強(qiáng)制刷新到主內(nèi)存中去,每次讀取前必須先從主存刷新最新的值。
2、保證不了原子性:替代不了鎖。
3、禁止指令重排:避免指令并行運(yùn)行。
ThreadLocal
ThreadLocal叫做線程變量,意思是ThreadLocal中填充的變量屬于當(dāng)前線程,該變量對(duì)其他線程而言是隔離的,也就是說(shuō)該變量是當(dāng)前線程獨(dú)有的變量。ThreadLocal為變量在每個(gè)線程中都創(chuàng)建了一個(gè)副本,那么每個(gè)線程可以訪問(wèn)自己內(nèi)部的副本變量。
ThreadLocal使用場(chǎng)景
1、管理Session會(huì)話:將Session保存在ThreadLocal中,使線程處理多次會(huì)話時(shí)始終是同一個(gè)Session。
2、JDBC 連接 Connection,這樣就可以保證每個(gè)線程的都在各自的 Connection 上進(jìn)行數(shù)據(jù)庫(kù)的操作,不會(huì)出現(xiàn) A 線程關(guān)了 B線程正在使用的 Connection。
ThreadLocal與Synchronized的區(qū)別
ThreadLocal<T>其實(shí)是與線程綁定的一個(gè)變量。ThreadLocal和Synchonized都用于解決多線程并發(fā)訪問(wèn)。
但是ThreadLocal與synchronized有本質(zhì)的區(qū)別:
1、Synchronized用于線程間的數(shù)據(jù)共享,而ThreadLocal則用于線程間的數(shù)據(jù)隔離。
2、Synchronized是利用鎖的機(jī)制,使變量或代碼塊在某一時(shí)該只能被一個(gè)線程訪問(wèn)。而ThreadLocal為每一個(gè)線程都提供了變量的副本,使得每個(gè)線程在某一時(shí)間訪問(wèn)到的并不是同一個(gè)對(duì)象,這樣就隔離了多個(gè)線程對(duì)數(shù)據(jù)的數(shù)據(jù)共享。
而Synchronized卻正好相反,它用于在多個(gè)線程間通信時(shí)能夠獲得數(shù)據(jù)共享。
ThreadLocal內(nèi)存泄露
每個(gè)線程都有?個(gè)ThreadLocalMap的內(nèi)部屬性,map的key是ThreaLocal,定義為弱引用,value是強(qiáng)引用類型。垃圾回收的時(shí)候會(huì)自動(dòng)回收key,而value的回收取決于Thread對(duì)象的生命周期。一般會(huì)通過(guò)線程池的方式復(fù)用線程節(jié)省資源,這也就導(dǎo)致了線程對(duì)象的生命周期比較長(zhǎng),這樣便一直存在一條強(qiáng)引用鏈的關(guān)系:Thread --> ThreadLocalMap-->Entry-->Value,隨著任務(wù)的執(zhí)行,value就有可能越來(lái)越多且無(wú)法釋放,最終導(dǎo)致內(nèi)存泄漏。
ThreadLocal正確使用方法
1、每次使用完ThreadLocal都調(diào)用它的remove()方法清除數(shù)據(jù)
2、將ThreadLocal變量定義成private static,這樣就一直存在ThreadLocal的強(qiáng)引用,也就能保證任何時(shí)候都能通過(guò)ThreadLocal的弱引用訪問(wèn)到Entry的value值,進(jìn)而清除掉 。
FutureTask
FutureTask 表示一個(gè)異步運(yùn)算的任務(wù)。FutureTask 里面可以傳入一個(gè) Callable 的具體實(shí)現(xiàn)類,可以對(duì)這個(gè)異步運(yùn)算的任務(wù)的結(jié)果進(jìn)行等待獲取、判斷是否已經(jīng)完成、取消任務(wù)等操作。只有當(dāng)運(yùn)算完成的時(shí)候結(jié)果才能取回,如果運(yùn)算尚未完成 get 方法將會(huì)阻塞。一個(gè) FutureTask 對(duì)象可以對(duì)調(diào)用了 Callable 和 Runnable 的對(duì)象進(jìn)行包裝,由于 FutureTask 也是Runnable 接口的實(shí)現(xiàn)類,所以 FutureTask 也可以放入線程池中。
synchronized、Lock
1、synchronized是Java關(guān)鍵字,在JVM層面實(shí)現(xiàn)加鎖和解鎖;Lock是一個(gè)接口,在代碼層面實(shí)現(xiàn)加鎖和解鎖。
2、synchronized可以用在代碼塊上、方法上;Lock只能寫在代碼里。
3、synchronized在代碼執(zhí)行完或出現(xiàn)異常時(shí)自動(dòng)釋放鎖;Lock不會(huì)自動(dòng)釋放鎖,需要在finally中顯示釋放鎖。
4、synchronized會(huì)導(dǎo)致線程拿不到鎖一直等待;Lock可以設(shè)置獲取鎖失敗的超時(shí)時(shí)間。
5、synchronized無(wú)法得知是否獲取鎖成功;Lock則可以通過(guò)tryLock得知加鎖是否成功。
6、synchronized鎖可重入、不可中斷、非公平;Lock鎖可重入、可中斷、可公平/不公平,并可以細(xì)分讀寫鎖以提高效率。
1、公平性:lock支持公平鎖,sync不支持公平鎖
2、鎖狀態(tài):lock可以獲取鎖狀態(tài),sync無(wú)法獲取鎖狀態(tài)
3、性能:資源競(jìng)爭(zhēng)激烈時(shí)lock性能優(yōu)異,資源競(jìng)爭(zhēng)不激烈時(shí)sync性能優(yōu)異。
synchronized實(shí)現(xiàn)原理
1、synchronized作用在代碼塊時(shí),它的底層是通過(guò)monitorenter、monitorexit指令來(lái)實(shí)現(xiàn)的。
monitorenter:每個(gè)對(duì)象都是一個(gè)監(jiān)視器鎖(monitor),當(dāng)monitor被占用時(shí)就會(huì)處于鎖定狀態(tài),線程執(zhí)行monitorenter指令時(shí)嘗試獲取monitor的所有權(quán),過(guò)程如下:如果monitor的進(jìn)入數(shù)為0,則該線程進(jìn)入monitor,然后將進(jìn)入數(shù)設(shè)置為1,該線程即為monitor的所有者。如果線程已經(jīng)占有該monitor,只是重新進(jìn)入,則進(jìn)入monitor的進(jìn)入數(shù)加1。如果其他線程已經(jīng)占用了monitor,則該線程進(jìn)入阻塞狀態(tài),直到monitor的進(jìn)入數(shù)為0,再重新嘗試獲取monitor的所有權(quán)。
monitorexit:執(zhí)行monitorexit的線程必須是objectref所對(duì)應(yīng)的monitor持有者。指令執(zhí)行時(shí),monitor的進(jìn)入數(shù)減1,如果減1后進(jìn)入數(shù)為0,那線程退出monitor,不再是這個(gè)monitor的所有者。其他被這個(gè)monitor阻塞的線程可以嘗試去獲取這個(gè)monitor的所有權(quán)。
2、方法的同步并沒有通過(guò) monitorenter 和 monitorexit 指令來(lái)完成,不過(guò)相對(duì)于普通方法,其常量池中多了 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ì)象。
synchronized和CAS的區(qū)別?什么場(chǎng)景使用CAS?什么場(chǎng)景使用synchronized
簡(jiǎn)單的來(lái)說(shuō)CAS適用于寫比較少的情況下(多讀場(chǎng)景,沖突一般較少)
synchronized適用于寫比較多的情況下(多寫場(chǎng)景,沖突一般較多)
1.對(duì)于資源競(jìng)爭(zhēng)較少(線程沖突較輕)的情況,使用synchronized同步鎖進(jìn)行線程阻塞和喚醒切換以及用戶態(tài)內(nèi)核態(tài)間的切換操作額外浪費(fèi)消耗cpu資源;而CAS基于硬件實(shí)現(xiàn),不需要進(jìn)入內(nèi)核,不需要切換線程,操作自旋幾率較少,因此可以獲得更高的性能。
2.對(duì)于資源競(jìng)爭(zhēng)嚴(yán)重(線程沖突嚴(yán)重)的情況,CAS自旋的概率會(huì)比較大,從而浪費(fèi)更多的CPU資源,效率低于synchronized。
synchronized是如何實(shí)現(xiàn)可重入性的
synchronized關(guān)鍵字經(jīng)過(guò)編譯后,會(huì)在同步塊的前后分別形成monitorenter和monitorexit兩個(gè)字節(jié)碼指令。每個(gè)鎖對(duì)象內(nèi)部維護(hù)一個(gè)計(jì)數(shù)器,該計(jì)數(shù)器初始值為0,表示任何線程都可以獲取該鎖并執(zhí)行相應(yīng)的方法。根據(jù)虛擬機(jī)規(guī)范要求,在執(zhí)行monitorenter指令時(shí),首先要嘗試獲取對(duì)象的鎖,如果這個(gè)對(duì)象沒有被鎖定,或者當(dāng)前線程已經(jīng)擁有了對(duì)象的鎖,把鎖的計(jì)數(shù)器+1,相應(yīng)的在執(zhí)行monitorexit指令后鎖計(jì)數(shù)器-1,當(dāng)計(jì)數(shù)器為0時(shí),鎖就被釋放。如果獲取對(duì)象鎖失敗,那當(dāng)前線程就要阻塞等待,直到對(duì)象鎖被另一個(gè)線程釋放為止。
使用synchronized修飾靜態(tài)方法和非靜態(tài)方法的區(qū)別
1、Synchronized修飾非靜態(tài)方法,實(shí)際上是對(duì)調(diào)用該方法的對(duì)象加鎖,俗稱“對(duì)象鎖”。
<1> 同一個(gè)對(duì)象在兩個(gè)線程中分別訪問(wèn)該對(duì)象的兩個(gè)同步方法,會(huì)產(chǎn)生互斥。
<2> 不同對(duì)象在兩個(gè)線程中調(diào)用同一個(gè)同步方法,不會(huì)產(chǎn)生互斥。
2、Synchronized修飾靜態(tài)方法,實(shí)際上是對(duì)該類對(duì)象加鎖,俗稱“類鎖”。
<1> 用類直接在兩個(gè)線程中調(diào)用兩個(gè)不同的同步方法,會(huì)產(chǎn)生互斥。
<2> 用一個(gè)類的靜態(tài)對(duì)象在兩個(gè)線程中調(diào)用靜態(tài)方法或非靜態(tài)方法,會(huì)產(chǎn)生互斥。
<3> 一個(gè)對(duì)象在兩個(gè)線程中分別調(diào)用一個(gè)靜態(tài)同步方法和一個(gè)非靜態(tài)同步方法,不會(huì)產(chǎn)生互斥。
synchronized加在this和class區(qū)別
synchronized 加鎖 class 時(shí),無(wú)論共享一個(gè)對(duì)象還是創(chuàng)建多個(gè)對(duì)象,它們用的都是同一把鎖,而使用 synchronized 加鎖 this 時(shí),只有同一個(gè)對(duì)象會(huì)使用同一把鎖,不同對(duì)象之間的鎖是不同的。
線程安全集合
1、HashTable/Vector(性能較差已經(jīng)棄用)
2、Collections.synchroniedXXX方法包裝的集合。
3、JUC下面的以Concurrent/CopyOnWrite開頭的集合
4、BlockingQueue: BlockingQueue是阻塞隊(duì)列,內(nèi)部維護(hù)了一個(gè)RetrantLock和2個(gè)Condition來(lái)實(shí)現(xiàn)生產(chǎn)者和消費(fèi)者線程同步。當(dāng)隊(duì)列為空時(shí)消費(fèi)者線程阻塞等待生產(chǎn)者線程往隊(duì)列中添加元素,當(dāng)隊(duì)列滿時(shí)生產(chǎn)者線程阻塞等待消費(fèi)者線程從隊(duì)列中取元素。
<1> DelayQueue:基于時(shí)間優(yōu)先級(jí)的隊(duì)列,延期阻塞隊(duì)列
<2> ArrayBlockingQueue:基于數(shù)組的并發(fā)阻塞隊(duì)列
<3> LinkedBlockingQueue:基于鏈表的FIFO阻塞隊(duì)列
<4> PriorityBlockingQueue:帶優(yōu)先級(jí)的無(wú)界阻塞隊(duì)列
<5> SynchronousQueue:并發(fā)同步阻塞隊(duì)列
AQS
抽象隊(duì)列同步器AbstractQueuedSynchronizer ,為構(gòu)建鎖或者其他同步組件提供了一個(gè)框架,AQS采用模板方法模式,子類只需要實(shí)現(xiàn)特定的幾個(gè)方法來(lái)實(shí)現(xiàn)鎖或者同步邏輯。AQS內(nèi)部維護(hù)了一個(gè)int成員變量來(lái)表示同步狀態(tài),通過(guò)內(nèi)置的FIFO(first-in-first-out)同步隊(duì)列來(lái)控制獲取共享資源的線程?;贏QS實(shí)現(xiàn)的有RetrantLock、Semaphore、CyclicBarria,CountDownLatch。
四種同步器
1、CountDownLatch:就是等待其他線程執(zhí)行完任務(wù),并且必要時(shí)可以對(duì)各個(gè)任務(wù)的結(jié)果進(jìn)行匯總,然后主線程繼續(xù)往下執(zhí)行;
2、CyclicBarrier:可以讓一組線程達(dá)到一個(gè)屏障時(shí)被阻塞,直到最后一個(gè)線程到達(dá)屏障時(shí),所有線程才可以繼續(xù)執(zhí)行;根據(jù)字面意思就是回環(huán)。
3、信號(hào)量:信號(hào)量核心作用是控制并發(fā)執(zhí)行的線程數(shù)量。定義信號(hào)量的時(shí)候會(huì)初始化一個(gè)預(yù)設(shè)值n,即最多有n個(gè)線程同時(shí)執(zhí)行,如果將n設(shè)為1,就達(dá)到了互斥鎖的效果。
AQS加解鎖流程
1、加鎖:先通過(guò)tryAcquire來(lái)嘗試獲取鎖,如果獲取成功就可以執(zhí)行你的代碼邏輯了。如果獲取失敗,那么就創(chuàng)建一個(gè) Node 節(jié)點(diǎn),并把當(dāng)前 線程 設(shè)置到Node節(jié)點(diǎn)中,最后把Node節(jié)點(diǎn)添加到內(nèi)部維護(hù)的一個(gè)隊(duì)列尾部,并阻塞住Node對(duì)應(yīng)線程運(yùn)行。
2、解鎖:AQS內(nèi)部會(huì)從隊(duì)列頭部開始獲取下一個(gè)節(jié)點(diǎn),然后解除節(jié)點(diǎn)對(duì)應(yīng)的線程阻塞狀態(tài),然后把該節(jié)點(diǎn)設(shè)置成隊(duì)列的頭節(jié)點(diǎn)(這樣實(shí)現(xiàn)隊(duì)列頭節(jié)點(diǎn)出棧),最后該節(jié)點(diǎn)線程對(duì)應(yīng)的代碼邏輯得以繼續(xù)執(zhí)行。后面如此循環(huán),AQS隊(duì)列中所有節(jié)點(diǎn)對(duì)應(yīng)線程中鎖住的代碼塊得以順序執(zhí)行。
JUC
JUC是java提供的并發(fā)操作實(shí)現(xiàn)線程安全的包,主要包含以下類:
1、原子類
2、Lock鎖
3、線程池(ThreadPoolExecutor)
4、并發(fā)容器(Concurrent開頭的容器、CopyOnWrite開頭的容器、BlockingQueue)
5、同步器(CountDownLatch(允許一個(gè)或者多個(gè)線程等待其它線程操作完成)、信號(hào)量(控制同時(shí)訪問(wèn)資源的數(shù)量)、CyclicBarrier(控制一組線程達(dá)到一個(gè)屏障時(shí)被阻塞,先到達(dá)的線程等待后到達(dá)的線程,當(dāng)所有的線程都到達(dá)時(shí)才會(huì)繼續(xù)執(zhí)行))
線程池
常見的幾種線程池
1、newCachedThreadPool():創(chuàng)建一個(gè)具有緩存功能的線程池,系統(tǒng)根據(jù)需要?jiǎng)?chuàng)建線程,這些線程將會(huì)被緩存在線程池中。
2、newFixedThreadPool(int nThreads):創(chuàng)建一個(gè)可重用的、具有固定線程數(shù)的線程池。
3、newSingleThreadExecutor():創(chuàng)建一個(gè)只有單線程的線程池,它相當(dāng)于調(diào)用newFixedThread Pool()方法時(shí)傳入?yún)?shù)為1。
4、newScheduledThreadPool(int corePoolSize):創(chuàng)建具有指定線程數(shù)的線程池,它可以在指定延遲后執(zhí)行線程任務(wù)。
5、corePoolSize指池中所保存的線程數(shù),即使線程是空閑的也被保存在線程池內(nèi)。
6、newSingleThreadScheduledExecutor():創(chuàng)建只有一個(gè)線程的線程池,它可以在指定延遲后執(zhí)行線程任務(wù)。
線程池的工作流程
1、判斷核心線程池是否已滿,沒滿則創(chuàng)建一個(gè)新的工作線程來(lái)執(zhí)行任務(wù)。
2、判斷任務(wù)隊(duì)列是否已滿,沒滿則將新提交的任務(wù)添加在工作隊(duì)列。
3、判斷整個(gè)線程池是否已滿,沒滿則創(chuàng)建一個(gè)新的工作線程來(lái)執(zhí)行任務(wù),已滿則執(zhí)行飽和(拒絕)策略。
線程池的拒絕策略
1、AbortPolicy:丟棄任務(wù)并拋出RejectedExecutionException異常。
2、DiscardPolicy:也是丟棄任務(wù),但是不拋出異常。
3、DiscardOldestPolicy:丟棄隊(duì)列最前面的任務(wù),然后重新嘗試執(zhí)行任務(wù)(重復(fù)該過(guò)程)。
4、CallerRunsPolicy:由調(diào)用線程處理該任務(wù)。
線程池參數(shù)
1、corePoolSize(核心工作線程數(shù)):當(dāng)向線程池提交一個(gè)任務(wù)時(shí),若線程池已創(chuàng)建的線程數(shù)小于corePoolSize,即便此時(shí)存在空閑線程,也會(huì)通過(guò)創(chuàng)建一個(gè)新線程來(lái)執(zhí)行該任務(wù),直到已創(chuàng)建的線程數(shù)大于或等于corePoolSize時(shí)。
2、maximumPoolSize(最大線程數(shù)):線程池所允許的最大線程個(gè)數(shù)。當(dāng)隊(duì)列滿了,且已創(chuàng)建的線程數(shù)小于maximumPoolSize,則線程池會(huì)創(chuàng)建新的線程來(lái)執(zhí)行任務(wù)。另外,對(duì)于無(wú)界隊(duì)列,可忽略該參數(shù)。
3、keepAliveTime(多余線程存活時(shí)間):當(dāng)線程池中線程數(shù)大于核心線程數(shù)時(shí),線程的空閑時(shí)間如果超過(guò)線程存活時(shí)間,那么這個(gè)線程就會(huì)被銷毀,直到線程池中的線程數(shù)小于等于核心線程數(shù)。
4、workQueue(隊(duì)列):用于傳輸和保存等待執(zhí)行任務(wù)的阻塞隊(duì)列。
5、threadFactory(線程創(chuàng)建工廠):用于創(chuàng)建新線程。threadFactory創(chuàng)建的線程也是采用new Thread()方式,threadFactory創(chuàng)建的線程名都具有統(tǒng)一的風(fēng)格:pool-m-thread-n(m為線程池的編號(hào),n為線程池內(nèi)的線程編號(hào))。
6、handler(拒絕策略):當(dāng)線程池和隊(duì)列都滿了,再加入線程會(huì)執(zhí)行此策略
線程池的生命周期
1、Running:線程池正在運(yùn)行中。
2、SHUTDOWN:線程池中不再接收新任務(wù),等待隊(duì)列中所有任務(wù)執(zhí)行完畢。
3、STOP:立馬停止,清空隊(duì)列中的所有任務(wù),已有任務(wù)也不再執(zhí)行。
4、TIDING:線程池中隊(duì)列為空。
5、TERMINATE:線程池死亡。
線程池中的線程是怎么創(chuàng)建的?是一開始就隨著線程池的啟動(dòng)創(chuàng)建好的嗎?
每當(dāng)我們調(diào)用execute()方法添加一個(gè)任務(wù)時(shí),線程池會(huì)做如下判斷:·如果正在運(yùn)行的線程數(shù)量小于corePoolSize,那么馬上創(chuàng)建線程運(yùn)行這個(gè)任務(wù);如果正在運(yùn)行的線程數(shù)量大于或等于corePoolSize,那么將這個(gè)任務(wù)放入隊(duì)列;如果這時(shí)候隊(duì)列滿了,而且正在運(yùn)行的線程數(shù)量小于maximumPoolSize,那么還是要?jiǎng)?chuàng)建非核心線程立刻運(yùn)行這個(gè)任務(wù);·如果隊(duì)列滿了,而且正在運(yùn)行的線程數(shù)量大于或等于maximumPoolSize,那么線程池會(huì)拋出異常RejectExecutionException。當(dāng)一個(gè)線程完成任務(wù)時(shí),它會(huì)從隊(duì)列中取下一個(gè)任務(wù)來(lái)執(zhí)行。當(dāng)一個(gè)線程無(wú)事可做,超過(guò)一定的時(shí)間(keepAliveTime)時(shí),線程池會(huì)判斷。
如果當(dāng)前運(yùn)行的線程數(shù)大于corePoolSize,那么這個(gè)線程就被停掉。所以線程池的所有任務(wù)完成后,它最終會(huì)收縮到corePoolSize的大小。
線程池中阻塞隊(duì)列的長(zhǎng)度如何設(shè)置
要結(jié)合具體的業(yè)務(wù)場(chǎng)景要求,如果系統(tǒng)要求相應(yīng)塊的話,可以將隊(duì)列大小設(shè)置小一點(diǎn),比如0,如果系統(tǒng)要求沒那么快的話,可以設(shè)置大一點(diǎn)。建議采用tomcat的處理方式,core與max一致,先擴(kuò)容到max再放隊(duì)列,不過(guò)隊(duì)列長(zhǎng)度要根據(jù)使用場(chǎng)景設(shè)置一個(gè)上限值,如果響應(yīng)時(shí)間要求較高的系統(tǒng)可以設(shè)置為0。
線程池中submit()和execute() 方法有什么區(qū)別
1、相同點(diǎn):都可以開啟線程執(zhí)行池中的任務(wù)。
2、不同點(diǎn):
接收參數(shù):execute()只能執(zhí)行 Runnable 類型的任務(wù)。submit()可以執(zhí)行 Runnable 和 Callable 類型的任務(wù)。
返回值:submit()方法可以返回持有計(jì)算結(jié)果的 Future 對(duì)象,而execute()沒有
異常處理:submit()方便Exception處理
提交任務(wù)時(shí)線程池隊(duì)列已滿
有倆種可能:
1、如果使用的是無(wú)界隊(duì)列 LinkedBlockingQueue,也就是無(wú)界隊(duì)列的話,沒關(guān)系,繼續(xù)添加任務(wù)到阻塞隊(duì)列中等待執(zhí)行,因?yàn)?LinkedBlockingQueue 可以近乎認(rèn)為是一個(gè)無(wú)窮大的隊(duì)列,可以無(wú)限存放任務(wù)。
2、如果使用的是有界隊(duì)列比如 ArrayBlockingQueue,任務(wù)首先會(huì)被添加到ArrayBlockingQueue 中,ArrayBlockingQueue 滿了,會(huì)根據(jù)maximumPoolSize 的值增加線程數(shù)量,如果增加了線程數(shù)量還是處理不過(guò)來(lái),ArrayBlockingQueue 繼續(xù)滿,那么則會(huì)使用拒絕策略RejectedExecutionHandler 處理滿了的任務(wù),默認(rèn)是 AbortPolicy。
線程池的關(guān)閉方式
1、shutdownNow:會(huì)首先將線程池的狀態(tài)設(shè)置成STOP,然后嘗試停止所有的正在執(zhí)行或暫停任務(wù)的線程。
2、shutdown:將線程池的狀態(tài)設(shè)置成SHUTDOWN狀態(tài),然后中斷所有沒有正在執(zhí)行任務(wù)的線程。
線程池的任務(wù)執(zhí)行完成判斷
1、使用線程池的原生函數(shù)isTerminated();executor提供一個(gè)原生函數(shù)isTerminated()來(lái)判斷線程池中的任務(wù)是否全部完成。如果全部完成返回true,否則返回false。
2、使用重入鎖,維持一個(gè)公共計(jì)數(shù)。所有的普通任務(wù)維持一個(gè)計(jì)數(shù)器,當(dāng)任務(wù)完成時(shí)計(jì)數(shù)器加一(這里要加鎖),當(dāng)計(jì)數(shù)器的值等于任務(wù)數(shù)時(shí),這時(shí)所有的任務(wù)已經(jīng)執(zhí)行完畢了。
3、使用CountDownLatch。它的原理跟第二種方法類似,給CountDownLatch一個(gè)計(jì)數(shù)值,任務(wù)執(zhí)行完畢后,調(diào)用countDown()執(zhí)行計(jì)數(shù)值減一。最后執(zhí)行的任務(wù)在調(diào)用方法的開始調(diào)用await()方法,這樣整個(gè)任務(wù)會(huì)阻塞,直到這個(gè)計(jì)數(shù)值為零,才會(huì)繼續(xù)執(zhí)行。這種方式的缺點(diǎn)就是需要提前知道任務(wù)的數(shù)量。
4、submit向線程池提交任務(wù),使用Future判斷任務(wù)執(zhí)行狀態(tài)。使用submit向線程池提交任務(wù)與execute提交不同,submit會(huì)有Future類型的返回值。通過(guò)future.isDone()方法可以知道任務(wù)是否執(zhí)行完成。
線程池阻塞隊(duì)列選擇
1、ArrayBlockingQueue(常用):都是共用同一個(gè)鎖對(duì)象,由此也意味著兩者無(wú)法真正并行運(yùn)行。
2、LinkedBlockingQueue(常用):生產(chǎn)者端和消費(fèi)者端分別采用了獨(dú)立的鎖來(lái)控制數(shù)據(jù)同步,這也意味著在高并發(fā)
的情況下生產(chǎn)者和消費(fèi)者可以并行地操作隊(duì)列中的數(shù)據(jù),以此來(lái)提高整個(gè)隊(duì)列的并發(fā)性能。
3、DelayQueue:DelayQueue 中的元素只有當(dāng)其指定的延遲時(shí)間到了,才能夠從隊(duì)列中獲取到該元素。DelayQueue 是一個(gè)沒有大小限制的隊(duì)列,因此往隊(duì)列中插入數(shù)據(jù)的操作(生產(chǎn)者)永遠(yuǎn)不會(huì)被阻塞,而只有獲取數(shù)據(jù)的操作(消費(fèi)者)才會(huì)被阻
塞。
4、PriorityBlockingQueue:不會(huì)阻塞數(shù)據(jù)生產(chǎn)者,而只會(huì)在沒有可消費(fèi)的數(shù)據(jù)時(shí),阻塞數(shù)據(jù)的消費(fèi)者。
因此使用的時(shí)候要特別注意,生產(chǎn)者生產(chǎn)數(shù)據(jù)的速度絕對(duì)不能快于消費(fèi)者消費(fèi)數(shù)據(jù)的速度,否則時(shí)間一長(zhǎng),會(huì)最終耗盡所有的可用堆內(nèi)存空間。
5、SynchronousQueue:不存儲(chǔ)元素的阻塞隊(duì)列,也即單個(gè)元素的隊(duì)列。
6、LinkedTransferQueue:由鏈表組成的無(wú)界阻塞隊(duì)列。
7、LinkedBlockingDeque:由鏈表組成的雙向阻塞隊(duì)列。
鎖
ReentrantLock實(shí)現(xiàn)原理
ReentrantLock是基于AQS實(shí)現(xiàn)的,AQS即AbstractQueuedSynchronizer的縮寫,這個(gè)是個(gè)內(nèi)部實(shí)現(xiàn)了兩個(gè)隊(duì)列的抽象類,分別是同步隊(duì)列和條件隊(duì)列。其中同步隊(duì)列是一個(gè)雙向鏈表,里面儲(chǔ)存的是處于等待狀態(tài)的線程,正在排隊(duì)等待喚醒去獲取鎖,而條件隊(duì)列是一個(gè)單向鏈表,里面儲(chǔ)存的也是處于等待狀態(tài)的線程,只不過(guò)這些線程喚醒的結(jié)果是加入到了同步隊(duì)列的隊(duì)尾,AQS所做的就是管理這兩個(gè)隊(duì)列里面線程之間的等待狀態(tài)-喚醒的工作。
在同步隊(duì)列中,還存在2中模式,分別是獨(dú)占模式和共享模式,這兩種模式的區(qū)別就在于AQS在喚醒線程節(jié)點(diǎn)的時(shí)候是不是傳遞喚醒,這兩種模式分別對(duì)應(yīng)獨(dú)占鎖和共享鎖。
AQS是一個(gè)抽象類,所以不能直接實(shí)例化,當(dāng)我們需要實(shí)現(xiàn)一個(gè)自定義鎖的時(shí)候可以去繼承AQS然后重寫獲取鎖的方式和釋放鎖的方式還有管理state,而ReentrantLock就是通過(guò)重寫了AQS的tryAcquire和tryRelease方法實(shí)現(xiàn)的lock和unlock。
ReentrantLock是如何實(shí)現(xiàn)可重入性的
ReentrantLock使用內(nèi)部類Sync來(lái)管理鎖,所以真正的獲取鎖是由Sync的實(shí)現(xiàn)類控制的。Sync有兩個(gè)實(shí)現(xiàn),分別為NonfairSync(非公公平鎖)和FairSync(公平鎖)。Sync通過(guò)繼承AQS實(shí)現(xiàn),在AQS中維護(hù)了一個(gè)private volatile int state來(lái)計(jì)算重入次數(shù),避免頻繁的持有釋放操作帶來(lái)的線程問(wèn)題。
自旋鎖
自旋鎖的定義:當(dāng)一個(gè)線程嘗試去獲取某一把鎖的時(shí)候,如果這個(gè)鎖此時(shí)已經(jīng)被別人獲取(占用),那么此線程就無(wú)法獲取到這把鎖,該線程將會(huì)等待,間隔一段時(shí)間后會(huì)再次嘗試獲取。這種采用循環(huán)加鎖 -> 等待的機(jī)制被稱為自旋鎖
自旋鎖的原理比較簡(jiǎn)單,如果持有鎖的線程能在短時(shí)間內(nèi)釋放鎖資源,那么那些等待競(jìng)爭(zhēng)鎖的線程就不需要做內(nèi)核態(tài)和用戶態(tài)之間的切換進(jìn)入阻塞狀態(tài),它們只需要等一等(自旋),等到持有鎖的線程釋放鎖之后即可獲取,這樣就避免了用戶進(jìn)程和內(nèi)核切換的消耗
讀寫鎖
與傳統(tǒng)鎖不同的是讀寫鎖的規(guī)則是可以共享讀,但只能一個(gè)寫,總結(jié)起來(lái)為:讀讀不互斥、讀寫互斥、寫寫互斥,而一般的獨(dú)占鎖是:讀讀互斥、讀寫互斥、寫寫互斥,而場(chǎng)景中往往讀遠(yuǎn)遠(yuǎn)大于寫,讀寫鎖就是為了這種優(yōu)化而創(chuàng)建出來(lái)的一種機(jī)制。 注意是讀遠(yuǎn)遠(yuǎn)大于寫,一般情況下獨(dú)占鎖的效率低來(lái)源于高并發(fā)下對(duì)臨界區(qū)的激烈競(jìng)爭(zhēng)導(dǎo)致線程上下文切換。因此當(dāng)并發(fā)不是很高的情況下,讀寫鎖由于需要額外維護(hù)讀鎖的狀態(tài),可能還不如獨(dú)占鎖的效率高。因此需要根據(jù)實(shí)際情況選擇使用。
在Java中ReadWriteLock的主要實(shí)現(xiàn)為ReentrantReadWriteLock,其提供了以下特性:
公平性選擇:支持公平與非公平(默認(rèn))的鎖獲取方式,吞吐量非公平優(yōu)先于公平。
可重入:讀線程獲取讀鎖之后可以再次獲取讀鎖,寫線程獲取寫鎖之后可以再次獲取寫鎖。
可降級(jí):寫線程獲取寫鎖之后,其還可以再次獲取讀鎖,然后釋放掉寫鎖,那么此時(shí)該線程是讀鎖狀態(tài),也就是降級(jí)操作。
分段鎖
容器里有多把鎖,每一把鎖用于鎖容器其中一部分?jǐn)?shù)據(jù),那么當(dāng)多線程訪問(wèn)容器里不同數(shù)據(jù)段的數(shù)據(jù)時(shí),線程間就不會(huì)存在鎖競(jìng)爭(zhēng),從而可以有效的提高并發(fā)訪問(wèn)效率。這就是ConcurrentHashMap所使用的鎖分段技術(shù),首先將數(shù)據(jù)分成一段一段的存儲(chǔ),然后給每一段數(shù)據(jù)配一把鎖,當(dāng)一個(gè)線程占用鎖訪問(wèn)其中一個(gè)段數(shù)據(jù)的時(shí)候,其他段的數(shù)據(jù)也能被其他線程訪問(wèn)。
樂觀鎖和悲觀鎖
1、悲觀鎖:總是假設(shè)最壞的情況,每次去拿數(shù)據(jù)的時(shí)候都認(rèn)為別人會(huì)修改,所以每次在拿數(shù)據(jù)的時(shí)候都會(huì)上鎖,這樣別人想拿這個(gè)數(shù)據(jù)就會(huì)阻塞直到它拿到鎖。Java中悲觀鎖是通過(guò)synchronized關(guān)鍵字或Lock接口來(lái)實(shí)現(xiàn)的。
2、樂觀鎖:顧名思義,就是很樂觀,每次去拿數(shù)據(jù)的時(shí)候都認(rèn)為別人不會(huì)修改,所以不會(huì)上鎖,但是在更新的時(shí)候會(huì)判斷一下在此期間別人有沒有去更新這個(gè)數(shù)據(jù)。樂觀鎖適用于多讀的應(yīng)用類型,這樣可以提高吞吐量。在JDK1.5 中新增 java.util.concurrent (J.U.C)就是建立在CAS之上的。相對(duì)于對(duì)于 synchronized 這種阻塞算法,CAS是非阻塞算法的一種常見實(shí)現(xiàn)。所以J.U.C在性能上有了很大的提升。
偏向鎖
顧名思義,它會(huì)偏向于第一個(gè)訪問(wèn)鎖的線程,如果在運(yùn)行過(guò)程中,同步鎖只有一個(gè)線程訪問(wèn),不存在多線程爭(zhēng)用的情況,則線程是不需要觸發(fā)同步的,減少加鎖/解鎖的一些CAS操作(比如等待隊(duì)列的一些CAS操作),這種情況下,就會(huì)給線程加一個(gè)偏向鎖。 如果在運(yùn)行過(guò)程中,遇到了其他線程搶占鎖,則持有偏向鎖的線程會(huì)被掛起,JVM會(huì)消除它身上的偏向鎖,將鎖恢復(fù)到標(biāo)準(zhǔn)的輕量級(jí)鎖。
公平鎖與非公平鎖
1、公平鎖:每個(gè)線程獲取鎖的順序是按照線程訪問(wèn)鎖的先后順序獲取的,最前面的線程總是最先獲取到鎖。在java中RetarntLock可以設(shè)置為公平鎖。實(shí)現(xiàn)原理:先將線程自己添加到等待隊(duì)列的隊(duì)尾并休眠,當(dāng)某線程用完鎖之后,會(huì)去喚醒等待隊(duì)列中隊(duì)首的線程嘗試去獲取鎖,鎖的使用順序也就是隊(duì)列中的先后順序,在整個(gè)過(guò)程中,線程會(huì)從運(yùn)行狀態(tài)切換到休眠狀態(tài),再?gòu)男菝郀顟B(tài)恢復(fù)成運(yùn)行狀態(tài),但線程每次休眠和恢復(fù)都需要從用戶態(tài)轉(zhuǎn)換成內(nèi)核態(tài),而這個(gè)狀態(tài)的轉(zhuǎn)換是比較慢的,所以公平鎖的執(zhí)行速度會(huì)比較慢。
2、非公平鎖:每個(gè)線程獲取鎖的順序是隨機(jī)的,并不會(huì)遵循先來(lái)先得的規(guī)則,所有線程會(huì)競(jìng)爭(zhēng)獲取鎖。在Java中synchronized和RetrantLock可以設(shè)置為非公平鎖。實(shí)現(xiàn)原理:當(dāng)線程獲取鎖時(shí),會(huì)先通過(guò) CAS 嘗試獲取鎖,如果獲取成功就直接擁有鎖,如果獲取鎖失敗才會(huì)進(jìn)入等待隊(duì)列,等待下次嘗試獲取鎖。這樣做的好處是,獲取鎖不用遵循先到先得的規(guī)則,從而避免了線程休眠和恢復(fù)的操作,這樣就加速了程序的執(zhí)行效率。
共享式與獨(dú)占式鎖
同一時(shí)刻獨(dú)占式只能有一個(gè)線程獲取同步狀態(tài),而共享式在同一時(shí)刻可以有多個(gè)線程獲取同步狀態(tài)。例如讀操作可以有多個(gè)線程同時(shí)進(jìn)行,而寫操作同一時(shí)刻只能有一個(gè)線程進(jìn)行寫操作,其他操作都會(huì)被阻塞。
synchronized 鎖升級(jí)
1、synchronized 鎖升級(jí)原理:在鎖對(duì)象的對(duì)象頭里面有一個(gè) threadid 字段,在第一次訪問(wèn)的時(shí)候 threadid 為空,jvm 讓其持有偏向鎖,并將 threadid 設(shè)置為其線程 id,再次進(jìn)入的時(shí)候會(huì)先判斷 threadid 是否與其線程 id 一致,如果一致則可以直接使用此對(duì)象,如果不一致,則升級(jí)偏向鎖為輕量級(jí)鎖,通過(guò)自旋循環(huán)一定次數(shù)來(lái)獲取鎖,執(zhí)行一定次數(shù)之后,如果還沒有正常獲取到要使用的對(duì)象,此時(shí)就會(huì)把鎖從輕量級(jí)升級(jí)為重量級(jí)鎖,此過(guò)程就構(gòu)成了 synchronized 鎖的升級(jí)。
2、鎖的升級(jí)的目的:鎖升級(jí)是為了減低了鎖帶來(lái)的性能消耗。在 Java 6 之后優(yōu)化 synchronized 的實(shí)現(xiàn)方式,使用了偏向鎖升級(jí)為輕量級(jí)鎖再升級(jí)到重量級(jí)鎖的方式,從而減低了鎖帶來(lái)的性能消耗。
死鎖
1、定義:多個(gè)進(jìn)程或者線程在競(jìng)爭(zhēng)資源時(shí)產(chǎn)生了資源依賴而導(dǎo)致互相等待的現(xiàn)象,如果沒有外力介入,就會(huì)一直等待下去無(wú)法繼續(xù)運(yùn)行。
2、條件:<1>資源在一段時(shí)間只能被一個(gè)進(jìn)程占據(jù) <2>當(dāng)某個(gè)資源被某個(gè)進(jìn)程占用之后其它進(jìn)程只能等待 <3>某個(gè)資源被占用之后,只能等待該進(jìn)程釋放 <4>循環(huán)等待
如何避免線程死鎖
1、避免一個(gè)線程同時(shí)獲得多個(gè)鎖
2、避免一個(gè)線程在鎖內(nèi)同時(shí)占用多個(gè)資源,盡量保證每個(gè)鎖只占用一個(gè)資源
3、嘗試使用定時(shí)鎖,使用lock.tryLock(timeout)來(lái)替代使用內(nèi)部鎖機(jī)制
CAS
1、CAS 是 compare and swap 的縮寫,即我們所說(shuō)的比較交換。
2、cas 是一種基于鎖的操作,而且是樂觀鎖。在 java 中鎖分為樂觀鎖和悲觀鎖。悲觀鎖是將資源鎖住,等一個(gè)之前獲得鎖的線程釋放鎖之后,下一個(gè)線程才可以訪問(wèn)。而樂觀鎖采取了一種寬泛的態(tài)度,通過(guò)某種方式不加鎖來(lái)處理資源,比如通過(guò)給記錄加 version 來(lái)獲取數(shù)據(jù),性能較悲觀鎖有很大的提高。
3、CAS 操作包含三個(gè)操作數(shù) —— 內(nèi)存位置(V)、預(yù)期原值(A)和新值(B)。如果內(nèi)存地址里面的值和 A 的值是一樣的,那么就將內(nèi)存里面的值更新成 B。CAS是通過(guò)無(wú)限循環(huán)來(lái)獲取數(shù)據(jù)的,若果在第一輪循環(huán)中,a 線程獲取地址里面的值被b 線程修改了,那么 a 線程需要自旋,到下次循環(huán)才有可能機(jī)會(huì)執(zhí)行。
CAS問(wèn)題
1、ABA 問(wèn)題:
比如說(shuō)一個(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 操作成功,但可能存在潛藏的問(wèn)題。從 Java1.5 開始 JDK 的 atomic包里提供了一個(gè)類 AtomicStampedReference 來(lái)解決 ABA 問(wèn)題。
2、循環(huán)時(shí)間長(zhǎng)開銷大:對(duì)于資源競(jìng)爭(zhēng)嚴(yán)重(線程沖突嚴(yán)重)的情況,CAS 自旋的概率會(huì)比較大,從而浪費(fèi)更多的 CPU 資源,效率低于 synchronized。
3、只能保證一個(gè)共享變量的原子操作:當(dāng)對(duì)一個(gè)共享變量執(zhí)行操作時(shí),我們可以使用循環(huán) CAS 的方式來(lái)保證原子操作,但是對(duì)多個(gè)共享變量操作時(shí),循環(huán) CAS 就無(wú)法保證操作的原子性,這個(gè)時(shí)候就可以用鎖。
LongAdder
1、LongAdder采用了分段鎖的思想,內(nèi)部維護(hù)了一組計(jì)數(shù)單元,不同的線程可以操作不同的計(jì)數(shù)單元,減少了線程競(jìng)爭(zhēng),提高了效率。最終LongAdder的數(shù)值等于base值+所有計(jì)數(shù)單元的值之和。
2、在線程數(shù)量比較少時(shí)AtomicLong效率要高一點(diǎn),在線程數(shù)量很多時(shí)LongAdder效率高一點(diǎn)。
其它
假如有一個(gè)第三方接口,有很多個(gè)線程去調(diào)用獲取數(shù)據(jù),現(xiàn)在規(guī)定每秒鐘最多有10個(gè)線程同
時(shí)調(diào)用它,如何做到?
可以使用ScheduledThreadPool的scheduleAtFixedRate方法。
用三個(gè)線程按順序循環(huán)打印abc三個(gè)字母,比如abcabcabc?
如果讓你實(shí)現(xiàn)一個(gè)并發(fā)安全的鏈表,你會(huì)怎么做?
有哪些無(wú)鎖數(shù)據(jù)結(jié)構(gòu),他們實(shí)現(xiàn)的原理是什么?
CAS實(shí)現(xiàn)的
簡(jiǎn)述ConcurrentLinkedQueue和LinkedBlockingQueue的用處和不同之處?
BlockingQue哪些操作時(shí)阻塞的
take和put是阻塞的。
poll和offer是非阻塞的。