符合死鎖的四個(gè)條件:
-
互斥條件:一個(gè)時(shí)刻一個(gè)線(xiàn)程一個(gè)資源
-
請(qǐng)求與保持條件:一個(gè)線(xiàn)程因請(qǐng)求資源而阻塞時(shí),對(duì)已獲得的資源保持不放。
-
不剝奪條件:線(xiàn)程已獲得的資源,在未用完之前,不能被其他線(xiàn)程剝奪。
-
循環(huán)等待條件:若干線(xiàn)程形成頭尾相接的循環(huán)等待資源關(guān)系。
如何預(yù)防和避免線(xiàn)程死鎖:
- 破壞請(qǐng)求與保持條件:一次性申請(qǐng)所有資源
- 破壞不剝奪條件:占用部分資源的線(xiàn)程進(jìn)一步申請(qǐng)其他資源時(shí),如果申請(qǐng)不到,可以主動(dòng)釋放它占有的資源。
- 靠按序申請(qǐng)資源來(lái)預(yù)防。按某一順序申請(qǐng)資源,釋放資源則反序釋放。破壞循環(huán)等待條件。
避免死鎖:
? 避免死鎖就是在資源分配時(shí),借助于算法(比如銀行家算法)對(duì)資源分配進(jìn)行計(jì)算評(píng)估,使其進(jìn)入安全狀態(tài)。
? 安全狀態(tài):指的是系統(tǒng)能夠按照某種線(xiàn)程推進(jìn)順序(P1,P2,P3...Pn)來(lái)為每個(gè)線(xiàn)程分配資源。知道每個(gè)線(xiàn)程對(duì)資源的最大需求,是每個(gè)線(xiàn)程都iiu可以順利完成。稱(chēng)< P1,P2...Pn>序列為安全序列。
sleep()方法沒(méi)有釋放鎖,wait()方法釋放了鎖。
可以直接調(diào)用Thread類(lèi)的run方法:
? 調(diào)用 start() 方法方可啟動(dòng)線(xiàn)程并使線(xiàn)程進(jìn)入就緒狀態(tài),直接執(zhí)行 run() 方法的話(huà)不會(huì)以多線(xiàn)程的方式執(zhí)行。
? run方法可以創(chuàng)建一個(gè)線(xiàn)程,但是相當(dāng)于同步的方式,沒(méi)有多線(xiàn)程的存在。
只有調(diào)用start方法才是交給jvm管理,才是多線(xiàn)程。
為什么需要內(nèi)存模型JMM(Java Memory Model)
? 一般來(lái)說(shuō),編程語(yǔ)言也可以直接復(fù)用操作系統(tǒng)層面的內(nèi)存模型。不過(guò),不同的操作系統(tǒng)內(nèi)存模型不同。如果直接復(fù)用操作系統(tǒng)層面的內(nèi)存模型,就可能會(huì)導(dǎo)致同樣一套代碼換了一個(gè)操作系統(tǒng)就無(wú)法執(zhí)行了。Java 語(yǔ)言是跨平臺(tái)的,它需要自己提供一套內(nèi)存模型以屏蔽系統(tǒng)差異。
? 這只是 JMM 存在的其中一個(gè)原因。實(shí)際上,對(duì)于 Java 來(lái)說(shuō),你可以把 JMM 看作是 Java 定義的并發(fā)編程相關(guān)的一組規(guī)范,除了抽象了線(xiàn)程和主內(nèi)存之間的關(guān)系之外,其還規(guī)定了從 Java 源代碼到 CPU 可執(zhí)行指令的這個(gè)轉(zhuǎn)化過(guò)程要遵守哪些和并發(fā)相關(guān)的原則和規(guī)范,其主要目的是為了簡(jiǎn)化多線(xiàn)程編程,增強(qiáng)程序可移植性的。
? 簡(jiǎn)化多線(xiàn)程編程和增強(qiáng)程序可移植性的
happen-before原則
? happens-before 原則表達(dá)的意義其實(shí)并不是一個(gè)操作發(fā)生在另外一個(gè)操作的前面,雖然這從程序員的角度上來(lái)說(shuō)也并無(wú)大礙。更準(zhǔn)確地來(lái)說(shuō),它更想表達(dá)的意義是前一個(gè)操作的結(jié)果對(duì)于后一個(gè)操作是可見(jiàn)的,無(wú)論這兩個(gè)操作是否在同一個(gè)線(xiàn)程里。
volatile 的作用
- ? 保證變量的內(nèi)存可見(jiàn)性
- ? 禁止指令重排序
volatile 關(guān)鍵字能保證數(shù)據(jù)的可見(jiàn)性,但不能保證數(shù)據(jù)的原子性。synchronized 關(guān)鍵字兩者都能保證。
樂(lè)觀鎖和悲觀鎖
悲觀鎖
? 像 Java 中synchronized和ReentrantLock等獨(dú)占鎖就是悲觀鎖思想的實(shí)現(xiàn)。
樂(lè)觀鎖
? 版本號(hào)機(jī)制和CAS算法。
CAS算法:
? Compare And Swap
? 函數(shù)公式:CAS(V,E,N)V:表示要更新的變量E:表示預(yù)期值N:表示新值。
版本號(hào)機(jī)制:
ABA問(wèn)題
LongAdder:
樂(lè)觀鎖總結(jié)
哈哈總結(jié)
理論上來(lái)說(shuō):
- 悲觀鎖通常多用于寫(xiě)比較多的情況下(多寫(xiě)場(chǎng)景,競(jìng)爭(zhēng)激烈),這樣可以避免頻繁失敗和重試影響性能,悲觀鎖的開(kāi)銷(xiāo)是固定的。不過(guò),如果樂(lè)觀鎖解決了頻繁失敗和重試這個(gè)問(wèn)題的話(huà)(比如
LongAdder),也是可以考慮使用樂(lè)觀鎖的,要視實(shí)際情況而定。 - 樂(lè)觀鎖通常多于寫(xiě)比較少的情況下(多讀場(chǎng)景,競(jìng)爭(zhēng)較少),這樣可以避免頻繁加鎖影響性能。不過(guò),樂(lè)觀鎖主要針對(duì)的對(duì)象是單個(gè)共享變量(參考
java.util.concurrent.atomic包下面的原子變量類(lèi))
(AbstractQueuedSynchronizer)
? 主要用來(lái)構(gòu)造鎖和同步器。比如:ReentrantLock ,Semaphore。
AQS核心思想
- ? AQS 核心思想是,如果被請(qǐng)求的共享資源空閑,則將當(dāng)前請(qǐng)求資源的線(xiàn)程設(shè)置為有效的工作線(xiàn)程,并且將共享資源設(shè)置為鎖定狀態(tài)。如果被請(qǐng)求的共享資源被占用,那么就需要一套線(xiàn)程阻塞等待以及被喚醒時(shí)鎖分配的機(jī)制,這個(gè)機(jī)制 AQS 是基于 CLH 鎖 (Craig, Landin, and Hagersten locks) 實(shí)現(xiàn)的。
- CLH鎖: 其實(shí)是一個(gè)虛擬的雙向隊(duì)列(虛擬即不存在實(shí)例,僅存在節(jié)點(diǎn)之間的關(guān)聯(lián)關(guān)系)。暫時(shí)獲取不到鎖得線(xiàn)程被加入到該隊(duì)列中。AQS 將每條請(qǐng)求共享資源的線(xiàn)程封裝成一個(gè) CLH 隊(duì)列鎖的一個(gè)結(jié)點(diǎn)(Node)來(lái)實(shí)現(xiàn)鎖的分配。在 CLH 隊(duì)列鎖中,一個(gè)節(jié)點(diǎn)表示一個(gè)線(xiàn)程,它保存著線(xiàn)程的引用(thread)、 當(dāng)前節(jié)點(diǎn)在隊(duì)列中的狀態(tài)(waitStatus)、前驅(qū)節(jié)點(diǎn)(prev)、后繼節(jié)點(diǎn)(next)。
- AQS使用int成員變量state表示同步狀態(tài),通過(guò)內(nèi)置得FIFO線(xiàn)程等待/等待線(xiàn)程來(lái)完成獲取資源現(xiàn)成的排隊(duì)工作。
AQS資源共享方式
-
獨(dú)占Exclusive(只有一個(gè)線(xiàn)程能執(zhí)行,如ReentrantLock)
-
共享Share(多個(gè)線(xiàn)程可以給同時(shí)執(zhí)行,如Semaphore/CountDownLatch)
常見(jiàn)同步工具類(lèi)
Semaphore(信號(hào)量)
? synchronized 和 ReentrantLock 都是一次只允許一個(gè)線(xiàn)程訪問(wèn)某個(gè)資源,而Semaphore(信號(hào)量)可以用來(lái)控制同時(shí)訪問(wèn)特定資源的線(xiàn)程數(shù)量。
? Semaphore 通常用于那些資源有明確訪問(wèn)數(shù)量限制的場(chǎng)景比如限流(僅限于單機(jī)模式,實(shí)際項(xiàng)目中推薦使用 Redis +Lua 來(lái)做限流)。
原理
? Semaphore是共享鎖的一種實(shí)現(xiàn),默認(rèn)構(gòu)造的AQS的state值為permits。
? 以無(wú)參 acquire 方法為例,調(diào)用semaphore.acquire() ,線(xiàn)程嘗試獲取許可證,如果 state > 0 的話(huà),則表示可以獲取成功,如果 state <= 0 的話(huà),則表示許可證數(shù)量不足,獲取失敗。
? 如果可以獲取成功的話(huà)(state > 0 ),會(huì)嘗試使用 CAS 操作去修改 state 的值 state=state-1。如果獲取失敗則會(huì)創(chuàng)建一個(gè) Node 節(jié)點(diǎn)加入等待隊(duì)列,掛起當(dāng)前線(xiàn)程。
?
ReentrantLock 與synchronized的區(qū)別(增加了那些高級(jí)功能)
? 等待可中斷:lock.lockInterruptibly()。synchronized不可中斷。
? 可實(shí)現(xiàn)公平鎖。
? 可實(shí)現(xiàn)選擇性通知(鎖可以綁定多個(gè)條件) synchronized=>wait(),notify()/notifyAll(),等待通知,ReentrantLocak=>Condtiton。使用notify()/notifyAll()方法進(jìn)行通知時(shí),被通知的線(xiàn)程是由 JVM 選擇的,用ReentrantLock類(lèi)結(jié)合Condition實(shí)例可以實(shí)現(xiàn)選擇性通知。使用notify()/notifyAll()方法進(jìn)行通知時(shí),被通知的線(xiàn)程是由 JVM 選擇的,用ReentrantLock類(lèi)結(jié)合Condition實(shí)例可以實(shí)現(xiàn)“選擇性通知”
ReentrantReadWriteLock
? 其實(shí)是兩把鎖:讀鎖(共享鎖(一把鎖可以被多個(gè)線(xiàn)程同時(shí)獲得))和寫(xiě)鎖(獨(dú)占鎖)
? 底層也是AQS。
讀鎖拿到,能不能拿到寫(xiě)鎖(不能);相反分情況。
? 在線(xiàn)程持有讀鎖的情況下,該線(xiàn)程不能取得寫(xiě)鎖(因?yàn)楂@取寫(xiě)鎖的時(shí)候,如果發(fā)現(xiàn)當(dāng)前的讀鎖被占用,就馬上獲取失敗,不管讀鎖是不是被當(dāng)前線(xiàn)程持有)。
? 在線(xiàn)程持有寫(xiě)鎖的情況下,該線(xiàn)程可以繼續(xù)獲取讀鎖(獲取讀鎖時(shí)如果發(fā)現(xiàn)寫(xiě)鎖被占用,只有寫(xiě)鎖沒(méi)有被當(dāng)前線(xiàn)程占用的情況才會(huì)獲取失敗)
讀鎖為什么不能升級(jí)為寫(xiě)鎖
? 可降級(jí)不可升級(jí)。
? 因?yàn)樯?jí)會(huì)導(dǎo)致,引發(fā)線(xiàn)程的爭(zhēng)奪(會(huì)導(dǎo)致死鎖);并且寫(xiě)鎖是獨(dú)占鎖,回影響性能。
StampedLock
? jdk1.8引入,讀寫(xiě)鎖;不可重入;不支持條件變量Condition。
? 不同于一般Lock類(lèi),不是直接實(shí)現(xiàn)Lock或ReadWriteLock接口,基于CLH鎖(AQS也是基于這個(gè))獨(dú)立實(shí)現(xiàn)的。
三種讀寫(xiě)控制模式:讀鎖,寫(xiě)鎖,樂(lè)觀鎖。
-
寫(xiě)鎖:獨(dú)占鎖(一把鎖只能被一個(gè)線(xiàn)程獲取)。當(dāng)一個(gè)線(xiàn)程獲取寫(xiě)鎖后,其他請(qǐng)求讀鎖和寫(xiě)鎖的線(xiàn)程必須等待。類(lèi)似于
ReentrantReadWriteLock的寫(xiě)鎖,不過(guò)這里的寫(xiě)鎖是不可重入的。 -
讀鎖(悲觀鎖):共享鎖,沒(méi)有線(xiàn)程獲取寫(xiě)鎖的情況下,多個(gè)線(xiàn)程可以同時(shí)持有讀鎖。如果己經(jīng)有線(xiàn)程持有寫(xiě)鎖,則其他線(xiàn)程請(qǐng)求獲取該讀鎖會(huì)被阻塞。類(lèi)似于
ReentrantReadWriteLock的讀鎖,不過(guò)這里的讀鎖是不可重入的。 -
樂(lè)觀讀:允許多個(gè)線(xiàn)程獲取樂(lè)觀讀以及讀鎖。同時(shí)允許一個(gè)寫(xiě)線(xiàn)程獲取寫(xiě)鎖。
StampedLock實(shí)現(xiàn)了不僅多個(gè)讀不互相阻塞,同時(shí)在讀操作時(shí)不會(huì)阻塞寫(xiě)操作。能夠達(dá)到這種效果,它的核心思想在于,**在讀的時(shí)候如果發(fā)生了寫(xiě),應(yīng)該通過(guò)重試的方式來(lái)獲取新的值,而不應(yīng)該阻塞寫(xiě)操作。
這種操作方式?jīng)Q定了StampedLock在讀線(xiàn)程非常多而寫(xiě)線(xiàn)程非常少的場(chǎng)景下非常適用,同時(shí)還避免了寫(xiě)?zhàn)囸I情況的發(fā)生。
StampedLock不可重入tampedLock在獲取鎖的時(shí)候會(huì)返回一個(gè) long 型的數(shù)據(jù)戳,該數(shù)據(jù)戳用于稍后的鎖釋放參數(shù),如果返回的數(shù)據(jù)戳為 0 則表示鎖獲取失敗。當(dāng)前線(xiàn)程持有了鎖再次獲取鎖還是會(huì)返回一個(gè)新的數(shù)據(jù)戳,這也是StampedLock不可重入的原因。
? tampedLock 和 ReentrantReadWriteLock 一樣,StampedLock 同樣適合讀多寫(xiě)少的業(yè)務(wù)場(chǎng)景,可以作為 ReentrantReadWriteLock的替代品,性能更好。
StampedLock 適合什么場(chǎng)景?
java實(shí)現(xiàn)多線(xiàn)程的幾種方式
1繼承Thread類(lèi):
? 1.創(chuàng)建多個(gè)線(xiàn)程,調(diào)用start方法,啟動(dòng)線(xiàn)程的時(shí)候,并不是調(diào)用線(xiàn)程類(lèi)的run方法,而是調(diào)用了線(xiàn)程類(lèi)的start方法。那么我們能不能調(diào)用run方法呢?答案是肯定的,因?yàn)閞un方法是一個(gè)public聲明的方法,因此我們是可以調(diào)用的,但是如果我們調(diào)用了run方法,那么這個(gè)方法將會(huì)作為一個(gè)普通的方法被調(diào)用,并不會(huì)開(kāi)啟線(xiàn)程。這里實(shí)際上是采用了設(shè)計(jì)模式中的模板方法模式,Thread類(lèi)作為模板,而run方法是在變化的,因此放到子類(lèi)來(lái)實(shí)現(xiàn)。(能調(diào)用,但是就不算開(kāi)啟線(xiàn)程了)
? 2。指定線(xiàn)程名稱(chēng),不指定的話(huà),系統(tǒng)會(huì)默認(rèn)指定線(xiàn)程名,命名規(guī)則是Thread-N的形式。但是為了排查問(wèn)題方便,建議在創(chuàng)建線(xiàn)程的時(shí)候指定一個(gè)合理的線(xiàn)程名字。下面的代碼是不使用線(xiàn)程名的樣子。
2實(shí)現(xiàn)runnable接口:
? 1.將低程序的耦合度
? 2.Runnabl接口中只定義一個(gè)方法run方法。
? 3.利用線(xiàn)程任務(wù)和線(xiàn)程的控制分離,實(shí)現(xiàn)了線(xiàn)程的解耦。我們要實(shí)現(xiàn)一個(gè)線(xiàn)程,可以借助Thread類(lèi),Thread類(lèi)要執(zhí)行的任務(wù)就可以由實(shí)現(xiàn)了Runnable接口的類(lèi)來(lái)處理。
? 4.1)創(chuàng)建線(xiàn)程任務(wù)
? 2)創(chuàng)建可運(yùn)行類(lèi):
線(xiàn)程任務(wù)和線(xiàn)程的控制分離,那么一個(gè)線(xiàn)程任務(wù)可以提交給多個(gè)線(xiàn)程來(lái)執(zhí)行。這是很有用的,比如車(chē)站的售票窗口,每個(gè)窗口可以看做是一個(gè)線(xiàn)程,他們每個(gè)窗口做的事情都是一樣的,也就是售票。這樣我們程序在模擬現(xiàn)實(shí)的時(shí)候就可以定義一個(gè)售票任務(wù),讓多個(gè)窗口同時(shí)執(zhí)行這一個(gè)任務(wù)。那么如果要改動(dòng)任務(wù)執(zhí)行計(jì)劃,只要修改線(xiàn)程任務(wù)類(lèi),所有的線(xiàn)程就都會(huì)按照修改后的來(lái)執(zhí)行。相比較繼承Thread類(lèi)的方式來(lái)創(chuàng)建線(xiàn)程的方式,實(shí)現(xiàn)Runnable接口是更為常用的。
? 3)lamba方式創(chuàng)建線(xiàn)程任務(wù):簡(jiǎn)化內(nèi)部類(lèi)的編寫(xiě)。
3使用內(nèi)部類(lèi)的方式
? 線(xiàn)程只想執(zhí)行一次的時(shí)候,就可以使用內(nèi)部類(lèi),可以少定義一個(gè)類(lèi)。就分為兩種情況:
? 1)繼承Thread類(lèi)
? 2)實(shí)現(xiàn)runnable接口。
? 當(dāng)兩者同時(shí)內(nèi)部實(shí)現(xiàn)時(shí),忽略runnable的方式。
代碼如下:
// 基于子類(lèi)的方式
new Thread() {
@Override
public void run() {
while (true) {
printThreadInfo();
}
}
}.start();
// 基于接口的實(shí)現(xiàn)
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
printThreadInfo();
}
}
}).start();
}
? lambda方式改造:
// 使用lambda的形式
new Thread(() -> {
while (true) {
printThreadInfo();
}
}).start();
// 對(duì)比不使用lambda的形式
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
printThreadInfo();
}
}
}).start();
4定時(shí)器
? 定時(shí)器可以說(shuō)是一種基于線(xiàn)程的一個(gè)工具類(lèi),可以定時(shí)的來(lái)執(zhí)行某個(gè)任務(wù)。在應(yīng)用中經(jīng)常需要定期執(zhí)行一些操作,比如要在凌晨的時(shí)候匯總一些數(shù)據(jù),比如要每隔10分鐘抓取一次某個(gè)網(wǎng)站上的數(shù)據(jù)等等,總之計(jì)時(shí)器無(wú)處不在。
? 1.指定時(shí)間點(diǎn)執(zhí)行
import java.text.SimpleDateFormat;
import java.util.Timer;
import java.util.TimerTask;
public class CreateThreadDemo9_Timer {
private static final SimpleDateFormat format =
new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
public static void main(String[] args) throws Exception {
```
// 創(chuàng)建定時(shí)器
Timer timer = new Timer();
// 提交計(jì)劃任務(wù)
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("定時(shí)任務(wù)執(zhí)行了...");
}
}, format.parse("2017-10-11 22:00:00"));
```
}
}
? 2.間隔時(shí)間重復(fù)執(zhí)行
// 提交計(jì)劃任務(wù)
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("定時(shí)任務(wù)執(zhí)行了...");
}
},
new Date(), 1000);
5帶返回值的線(xiàn)程實(shí)現(xiàn)方式(Callable接口)
-
imort java.util.concurrent.Callable; import java.util.concurrent.FutureTask; /** * 帶返回值的方式 */ public class CreateThreadDemo11_Callable { public static void main(String[] args) throws Exception { // 創(chuàng)建線(xiàn)程任務(wù) Callable<Integer> call = () -> { System.out.println("線(xiàn)程任務(wù)開(kāi)始執(zhí)行了...."); Thread.sleep(2000); return 1; }; // 將任務(wù)封裝為FutureTask FutureTask<Integer> task = new FutureTask<>(call); // 開(kāi)啟線(xiàn)程,執(zhí)行線(xiàn)程任務(wù) new Thread(task).start(); // ==================== // 這里是在線(xiàn)程啟動(dòng)之后,線(xiàn)程結(jié)果返回之前 System.out.println("這里可以為所欲為...."); // ==================== // 為所欲為完畢之后,拿到線(xiàn)程的執(zhí)行結(jié)果 Integer result = task.get(); System.out.println("主線(xiàn)程中拿到異步任務(wù)執(zhí)行的結(jié)果為:" + result); } }
6基于線(xiàn)程池的方式實(shí)現(xiàn)
我們知道,線(xiàn)程和數(shù)據(jù)庫(kù)連接這些資源都是非常寶貴的資源。那么每次需要的時(shí)候創(chuàng)建,不需要的時(shí)候銷(xiāo)毀,是非常浪費(fèi)資源的。那么我們就可以使用緩存的策略,也就是使用線(xiàn)程池。當(dāng)然了,線(xiàn)程池也不需要我們來(lái)實(shí)現(xiàn),jdk的官方也給我們提供了API。
?~~~java
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CreateThreadDemo12_ThreadPool {
public static void main(String[] args) throws Exception {
```
// 創(chuàng)建固定大小的線(xiàn)程池
ExecutorService threadPool = Executors.newFixedThreadPool(10);
while (true) {
// 提交多個(gè)線(xiàn)程任務(wù),并執(zhí)行
threadPool.execute(new Runnable() {
@Override
public void run() {
printThreadInfo();
}
});
}
```
}
/**
* 輸出當(dāng)前線(xiàn)程的信息
*/
private static void printThreadInfo() {
System.out.println("當(dāng)前運(yùn)行的線(xiàn)程名為: " + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
?~~~
?
ThreadLocal
?
? ThreadLocal類(lèi)主要解決的就是讓每個(gè)線(xiàn)程綁定自己的值。 訪問(wèn)一個(gè)threadLocal變量,則每一個(gè)線(xiàn)程都會(huì)有這個(gè)變量的本地副本。
? ThreadLocal類(lèi)主要解決的就是讓每個(gè)線(xiàn)程綁定自己的值,可以將ThreadLocal類(lèi)形象的比喻成存放數(shù)據(jù)的盒子,盒子中可以存儲(chǔ)每個(gè)線(xiàn)程的私有數(shù)據(jù)。
TheadLocal原理:
從上面Thread類(lèi) 源代碼可以看出Thread 類(lèi)中有一個(gè) threadLocals 和 一個(gè) inheritableThreadLocals 變量,它們都是 ThreadLocalMap 類(lèi)型的變量,我們可以把 ThreadLocalMap 理解為ThreadLocal 類(lèi)實(shí)現(xiàn)的定制化的 HashMap。默認(rèn)情況下這兩個(gè)變量都是 null,只有當(dāng)前線(xiàn)程調(diào)用 ThreadLocal 類(lèi)的 set或get方法時(shí)才創(chuàng)建它們,實(shí)際上調(diào)用這兩個(gè)方法的時(shí)候,我們調(diào)用的是ThreadLocalMap類(lèi)對(duì)應(yīng)的 get()、set()方法。
過(guò)上面這些內(nèi)容,我們足以通過(guò)猜測(cè)得出結(jié)論:最終的變量是放在了當(dāng)前線(xiàn)程的 ThreadLocalMap 中,并不是存在 ThreadLocal 上,ThreadLocal 可以理解為只是ThreadLocalMap的封裝,傳遞了變量值。 ThrealLocal 類(lèi)中可以通過(guò)Thread.currentThread()獲取到當(dāng)前線(xiàn)程對(duì)象后,直接通過(guò)getMap(Thread t)可以訪問(wèn)到該線(xiàn)程的ThreadLocalMap對(duì)象。
**每個(gè)Thread中都具備一個(gè)ThreadLocalMap,而ThreadLocalMap可以存儲(chǔ)以ThreadLocal為 key ,Object 對(duì)象為 value 的鍵值對(duì)。
ThreadLocal內(nèi)存泄漏問(wèn)題:
線(xiàn)程池
什么線(xiàn)程池
為什么使用線(xiàn)程池
? 池化技術(shù)的思想主要是為了減少每次獲取資源的消耗,提高對(duì)資源的利用率。
? 線(xiàn)程池提供了一種限制和管理資源(包括執(zhí)行一個(gè)任務(wù))的方式。 每個(gè)線(xiàn)程池還維護(hù)一些基本統(tǒng)計(jì)信息,例如已完成任務(wù)的數(shù)量。
創(chuàng)建線(xiàn)程池的方式
1通過(guò)ThreadPoolExecutor構(gòu)造函數(shù)來(lái)創(chuàng)建(推薦)。
2通過(guò) Executor 框架的工具類(lèi) Executors 來(lái)創(chuàng)建。
?
不推薦內(nèi)置線(xiàn)程池方法來(lái)創(chuàng)建線(xiàn)程池。
線(xiàn)程池常見(jiàn)參數(shù)
/**
* 用給定的初始參數(shù)創(chuàng)建一個(gè)新的ThreadPoolExecutor。
*/
public ThreadPoolExecutor(int corePoolSize,//線(xiàn)程池的核心線(xiàn)程數(shù)量
int maximumPoolSize,//線(xiàn)程池的最大線(xiàn)程數(shù)
long keepAliveTime,//當(dāng)線(xiàn)程數(shù)大于核心線(xiàn)程數(shù)時(shí),多余的空閑線(xiàn)程存活的最長(zhǎng)時(shí)間
TimeUnit unit,//時(shí)間單位
BlockingQueue<Runnable> workQueue,//任務(wù)隊(duì)列,用來(lái)儲(chǔ)存等待執(zhí)行任務(wù)的隊(duì)列
ThreadFactory threadFactory,//線(xiàn)程工廠,用來(lái)創(chuàng)建線(xiàn)程,一般默認(rèn)即可
RejectedExecutionHandler handler//拒絕策略,當(dāng)提交的任務(wù)過(guò)多而不能及時(shí)處理時(shí),我們可以定制策略來(lái)處理任務(wù)
) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
線(xiàn)程池的飽和策略
? 如果當(dāng)前同時(shí)運(yùn)行的線(xiàn)程數(shù)量達(dá)到最大線(xiàn)程數(shù)量并且隊(duì)列也已經(jīng)被放滿(mǎn)了任務(wù)時(shí),ThreadPoolTaskExecutor 定義一些策略:
線(xiàn)程池常用的阻塞隊(duì)列
?
應(yīng)為當(dāng)隊(duì)列無(wú)界是,既無(wú)法達(dá)到隊(duì)列的最大值,所以不需要擴(kuò)容核心線(xiàn)程數(shù),所以最大創(chuàng)建的線(xiàn)程為核心線(xiàn)程數(shù)。
線(xiàn)程池處理任務(wù)流程
如何設(shè)定線(xiàn)程池大小
? 多線(xiàn)程這個(gè)場(chǎng)景來(lái)說(shuō)主要是增加了上下文切換成本
如何動(dòng)態(tài)創(chuàng)建線(xiàn)程池參數(shù)(美團(tuán)的解決方案)
? 線(xiàn)程池參數(shù)動(dòng)態(tài)化。
動(dòng)態(tài)化的原理
經(jīng)過(guò)前面兩個(gè)方法的分析,我們知道了最大線(xiàn)程數(shù)和核心線(xiàn)程數(shù)可以動(dòng)態(tài)調(diào)整。
如何動(dòng)態(tài)指定等待隊(duì)列的長(zhǎng)度
?
ResizableCapacityLinkedBlockIngQueue 的隊(duì)列:
定義一個(gè)隊(duì)列,讓其可以對(duì) Capacity 參數(shù)進(jìn)行修改即可。操作起來(lái)也非常方便,把 LinkedBlockingQueue 粘貼一份出來(lái),修改個(gè)名字,然后把 Capacity 參數(shù)的 final 修飾符去掉,并提供其對(duì)應(yīng)的 get/set 方法。
Future類(lèi)
作用:
? 具體來(lái)說(shuō)是這樣的:當(dāng)我們執(zhí)行某一耗時(shí)的任務(wù)時(shí),可以將這個(gè)耗時(shí)任務(wù)交給一個(gè)子線(xiàn)程去異步執(zhí)行,同時(shí)我們可以干點(diǎn)其他事情,不用傻傻等待耗時(shí)任務(wù)執(zhí)行完成。等我們的事情干完后,我們?cè)偻ㄟ^(guò) Future 類(lèi)獲取到耗時(shí)任務(wù)的執(zhí)行結(jié)果。這樣一來(lái),程序的執(zhí)行效率就明顯提高了。
? 其實(shí)就是多線(xiàn)程中經(jīng)典的 Future 模式,你可以將其看作是一種設(shè)計(jì)模式,核心思想是異步調(diào)用,主要用在多線(xiàn)程領(lǐng)域,并非 Java 語(yǔ)言獨(dú)有。
Callable與Futrue的關(guān)系:
FutureTask 提供了 Future 接口的基本實(shí)現(xiàn),常用來(lái)封裝 Callable 和 Runnable,具有取消任務(wù)、查看任務(wù)是否執(zhí)行完成以及獲取任務(wù)執(zhí)行結(jié)果的方法。ExecutorService.submit() 方法返回的其實(shí)就是 Future 的實(shí)現(xiàn)類(lèi) FutureTask。
<T> Future<T> submit(Callable<T> task);
Future<?> submit(Runnable task);
FutureTask 有兩個(gè)構(gòu)造函數(shù),可傳入 Callable 或者 Runnable 對(duì)象。實(shí)際上,傳入 Runnable 對(duì)象也會(huì)在方法內(nèi)部轉(zhuǎn)換為Callable 對(duì)象。
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW;
}
public FutureTask(Runnable runnable, V result) {
// 通過(guò)適配器RunnableAdapter來(lái)將Runnable對(duì)象runnable轉(zhuǎn)換成Callable對(duì)象
this.callable = Executors.callable(runnable, result);
this.state = NEW;
}
FutureTask相當(dāng)于對(duì)Callable 進(jìn)行了封裝,管理著任務(wù)執(zhí)行的情況,存儲(chǔ)了 Callable 的 call 方法的任務(wù)執(zhí)行結(jié)果
CompletableFuture 類(lèi)的作用
AQS(AbstractQueuedSynchronizer)
? 抽象隊(duì)列同步器。java.util.concurrent.locks 包下面。
? AQS 就是一個(gè)抽象類(lèi),主要用來(lái)構(gòu)建鎖和同步器。
? AQS 為構(gòu)建鎖和同步器提供了一些通用功能的實(shí)現(xiàn),因此,使用 AQS 能簡(jiǎn)單且高效地構(gòu)造出應(yīng)用廣泛的大量的同步器,比如我們提到的 ReentrantLock,Semaphore,其他的諸如 ReentrantReadWriteLock,SynchronousQueue等等皆是基于 AQS 的。
AQS的原理
? 1.如果被請(qǐng)求的資源空閑,則當(dāng)前請(qǐng)求資源的線(xiàn)程設(shè)置為有效的工作線(xiàn)程,并且將共享資源設(shè)置為共享狀態(tài)。
? 2.如果請(qǐng)求得資源被占用時(shí),那么就需要一套線(xiàn)程阻塞等待以及被喚醒時(shí)鎖分配的機(jī)制。這個(gè)機(jī)制AQS就是用CLH隊(duì)列實(shí)現(xiàn)的,即將暫時(shí)獲取不到鎖的線(xiàn)程加入到隊(duì)列中。
? CLH隊(duì)列是一個(gè)虛擬的雙向隊(duì)列。一個(gè)線(xiàn)程相當(dāng)于一個(gè)節(jié)點(diǎn),一個(gè)節(jié)點(diǎn)保存著1)線(xiàn)程的引用 ;2)當(dāng)前節(jié)點(diǎn)在隊(duì)列中的狀態(tài),3)前驅(qū)節(jié)點(diǎn),4)后驅(qū)節(jié)點(diǎn)。
?

AQS使用int成員變量state表示同步狀態(tài),通過(guò)內(nèi)置的線(xiàn)程等待隊(duì)列來(lái)完成獲取資源線(xiàn)程的排隊(duì)工作。
state由volatile關(guān)鍵字修飾,用于展示當(dāng)前臨界資源的獲鎖情況。
// 共享變量,使用volatile修飾保證線(xiàn)程可見(jiàn)性
private volatile int state;
以CountDownLatch為例:
Semaphore的用處
? synchronized和ReentrantLock都是一次只允許一個(gè)線(xiàn)程訪問(wèn)某個(gè)資源。
? semaphore可以用來(lái)控制同時(shí)訪問(wèn)特定資源的線(xiàn)程數(shù)量。
當(dāng)初始資源個(gè)數(shù)為1時(shí),Semaphore退化為排他鎖。
兩種模式
? 1.公平模式:調(diào)用acquire方法的順序就是獲取許可證的順序,遵循FIFO。
? 2.非公平模式:搶占式的。
Semaphore的原理:
? 共享鎖的一種實(shí)現(xiàn),默認(rèn)構(gòu)造AQS的state的值為permits,可以將permits的值理解為許可證的數(shù)量,只有拿到許可證的線(xiàn)程才能執(zhí)行。
調(diào)用semaphore。acquire(),嘗試獲取許可證,
? 如果state>=0,表示可以獲取成功。
? 獲取成功的話(huà),使用CAS操作去修改state的值使得state=state-1。、
? 如果state<0,則表示許可證數(shù)量不足。此時(shí)加入阻塞隊(duì)列,掛起當(dāng)前線(xiàn)程。
CountDownLatch的用處
? CountDownLatch 允許 count 個(gè)線(xiàn)程阻塞在一個(gè)地方,直至所有線(xiàn)程的任務(wù)都執(zhí)行完畢。
CountDownLatch 是一次性的,計(jì)數(shù)器的值只能在構(gòu)造方法中初始化一次,之后沒(méi)有任何機(jī)制再次對(duì)其設(shè)置值,當(dāng) CountDownLatch 使用完畢后,它不能再次被使用。
CountDownLatch的原理
? 共享鎖的一種實(shí)現(xiàn)。默認(rèn)構(gòu)造AQS的state值為count。
CountDownLatch的場(chǎng)景:
偽代碼如下:
public class CountDownLatchExample1 {
// 處理文件的數(shù)量
private static final int threadCount = 6;
public static void main(String[] args) throws InterruptedException {
// 創(chuàng)建一個(gè)具有固定線(xiàn)程數(shù)量的線(xiàn)程池對(duì)象(推薦使用構(gòu)造方法創(chuàng)建)
ExecutorService threadPool = Executors.newFixedThreadPool(10);
final CountDownLatch countDownLatch = new CountDownLatch(threadCount);
for (int i = 0; i < threadCount; i++) {
final int threadnum = i;
threadPool.execute(() -> {
try {
//處理文件的業(yè)務(wù)操作
//......
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//表示一個(gè)文件已經(jīng)被完成
countDownLatch.countDown();
}
});
}
countDownLatch.await();
threadPool.shutdown();
System.out.println("finish");
}
}
CyclicBarrier的作用
? 可循環(huán)的屏障
CyclicBarrier的原理
? CyclicBarrier內(nèi)部通過(guò)一個(gè)count變量作為計(jì)數(shù)器,count的初始值為parties屬性為初始值,每當(dāng)一個(gè)線(xiàn)程到了柵欄這里了,那么就將計(jì)數(shù)器減 1。如果 count 值為 0 了,表示這是這一代最后一個(gè)線(xiàn)程到達(dá)柵欄,就嘗試執(zhí)行我們構(gòu)造方法中輸入的任務(wù)。
await的使用
在Java中,"await"方法通常與多線(xiàn)程編程和并發(fā)控制相關(guān)。這個(gè)方法通常用于等待某個(gè)條件或事件的發(fā)生,然后暫停當(dāng)前線(xiàn)程的執(zhí)行,直到條件滿(mǎn)足或事件發(fā)生后再繼續(xù)執(zhí)行。"await"方法通常與信號(hào)量、條件變量、鎖或線(xiàn)程池等并發(fā)控制機(jī)制一起使用,以實(shí)現(xiàn)線(xiàn)程的同步和協(xié)作。 在Java中,常見(jiàn)的使用"await"方法的類(lèi)和接口包括:
-
CountDownLatch(倒計(jì)數(shù)門(mén)閂):使用await()方法等待計(jì)數(shù)器歸零。
-
CyclicBarrier(循環(huán)屏障):使用await()方法等待到達(dá)指定的屏障位置。
-
Phaser(階段器):使用awaitAdvance()方法等待其他參與者到達(dá)相同的階段。
-
Condition(條件):在使用ReentrantLock或ReentrantReadWriteLock時(shí),可以使用await()方法等待條件滿(mǎn)足。
需要注意的是,"await"方法必須在某種線(xiàn)程同步機(jī)制的作用域內(nèi)使用,并且通常需要正確設(shè)置條件或事件的觸發(fā)條件,以避免線(xiàn)程永遠(yuǎn)等待或出現(xiàn)死鎖等并發(fā)問(wèn)題。
JMM(Java內(nèi)存模型)
CPU緩存模型
? 解決CPU處理速度和內(nèi)存不匹配的問(wèn)題。
? CPUCache工作方式可能會(huì)導(dǎo)致內(nèi)存緩存不一致性的問(wèn)題。
? 為了解決這個(gè)問(wèn)題:通過(guò)制定緩存一致協(xié)議(MESI協(xié)議),或其他手段。
?
Java線(xiàn)程池詳解
監(jiān)控線(xiàn)程池
? SpringBoot 中的 Actuator 組件。自己寫(xiě)一個(gè)監(jiān)控線(xiàn)程池的HealthContributor,(https://www.codenong.com/cs110918477/)
建議不同類(lèi)別的業(yè)務(wù)用不同的線(xiàn)程池
一個(gè)線(xiàn)程池執(zhí)行父子任務(wù)(父等子的回應(yīng),子等父的釋放資源)容易發(fā)生死鎖。
線(xiàn)程池和ThreadLocal共用的坑
Java常見(jiàn)并發(fā)容器總結(jié)
基本都在java.util.concurren包里。
ConcurrentHashMap
? 1.7版本Segment數(shù)組+HashEntry數(shù)組+鏈表
1.8版本Node數(shù)組+鏈表+紅黑樹(shù)
CopyOnWriteArrayList
Java1.5版本使用的是vetor,但是該容器增刪改查基本都加了synchornized.相當(dāng)于給vector加了一把大鎖,性能非常低下。
JUC中唯一的線(xiàn)程安全Lisy就是這個(gè)CopyOnWriteArrayList.
寫(xiě)時(shí)復(fù)制(Copy-On-Write)的策略(只有寫(xiě)寫(xiě)互斥);
ConcurrentLinkedQueue(非阻塞隊(duì)列)(無(wú)鎖)
? 阻塞隊(duì)列可以通過(guò)加鎖來(lái)實(shí)現(xiàn),非阻塞隊(duì)列可以通過(guò) CAS 操作實(shí)現(xiàn)。
主要使用CAS非阻塞算法來(lái)實(shí)現(xiàn)線(xiàn)程安全。其使用鏈表作為數(shù)據(jù)結(jié)構(gòu)。
BlockingQueue(阻塞隊(duì)列)(是一個(gè)接口)(以后好好看看)
ArrayBlockingQueue(實(shí)現(xiàn)類(lèi))(有界)
LinkedBlockingQueue(實(shí)現(xiàn)類(lèi)無(wú)界)(單向鏈表)
PriorityBlockingQueue(實(shí)現(xiàn)類(lèi)無(wú)界,支持優(yōu)先級(jí))![image-20230914102624707]()
ConcurrentSkipListMap
跳表(時(shí)間復(fù)雜度O(Logn))
? 所以在并發(fā)數(shù)據(jù)結(jié)構(gòu)中,JDK 使用跳表來(lái)實(shí)現(xiàn)一個(gè) Map。 
上圖是一個(gè)2級(jí)索引跳表
跳表是一種利用空間換時(shí)間的算法。
Atomic原子類(lèi)總結(jié)
? 在java.util.concurrent.atmoic包下面
原子類(lèi)可分為4類(lèi)。
一般都是通過(guò)CAS操作,比synchornized的操作開(kāi)銷(xiāo)小多了。
ThreadLocal詳解

具體使用
public class ThreadLocalTest {
private List<String> messages = Lists.newArrayList();
public static final ThreadLocal<ThreadLocalTest> holder = ThreadLocal.withInitial(ThreadLocalTest::new);
public static void add(String message) {
holder.get().messages.add(message);
}
public static List<String> clear() {
List<String> messages = holder.get().messages;
holder.remove();
System.out.println("size: " + holder.get().messages.size());
return messages;
}
public static void main(String[] args) {
ThreadLocalTest.add("一枝花算不算浪漫");
System.out.println(holder.get().messages);
ThreadLocalTest.clear();
}
}
需要理解的是:
public static final ThreadLocal<ThreadLocalTest> holder = ThreadLocal.withInitial(ThreadLocalTest::new);
? 當(dāng)我們創(chuàng)建一個(gè)ThreadLocal對(duì)象時(shí),我們可以使用withInitial方法為其指定一個(gè)供應(yīng)商函數(shù)(Supplier Function)。供應(yīng)商函數(shù)在需要的時(shí)候創(chuàng)建初始值,返回一個(gè)新的對(duì)象作為線(xiàn)程的局部變量。 在這個(gè)例子中,ThreadLocalTest.holder是一個(gè)ThreadLocal<ThreadLocalTest>對(duì)象,它的泛型參數(shù)是ThreadLocalTest,表示每個(gè)線(xiàn)程都將擁有一個(gè)ThreadLocalTest對(duì)象作為其局部變量。 ThreadLocalTest.holder使用了withInitial方法,并傳入了一個(gè)方法引用ThreadLocalTest::new作為供應(yīng)商函數(shù)。這意味著當(dāng)一個(gè)線(xiàn)程首次訪問(wèn)ThreadLocalTest.holder的get()方法時(shí),會(huì)調(diào)用ThreadLocalTest::new方法創(chuàng)建一個(gè)新的ThreadLocalTest對(duì)象作為該線(xiàn)程的局部變量。 換句話(huà)說(shuō),每個(gè)線(xiàn)程都有自己的ThreadLocalTest對(duì)象,通過(guò)ThreadLocalTest.holder.get()獲取。如果線(xiàn)程首次訪問(wèn)holder時(shí)還沒(méi)有創(chuàng)建過(guò)ThreadLocalTest對(duì)象,withInitial指定的供應(yīng)商函數(shù)會(huì)被調(diào)用來(lái)創(chuàng)建一個(gè)新的對(duì)象。而后續(xù)的訪問(wèn)會(huì)返回該線(xiàn)程已經(jīng)創(chuàng)建的那個(gè)對(duì)象。 這種方式保證了每個(gè)線(xiàn)程都擁有獨(dú)立的對(duì)象實(shí)例,而不會(huì)相互影響。每個(gè)線(xiàn)程都可以向自己的ThreadLocalTest對(duì)象的messages列表中添加消息,而不會(huì)影響其他線(xiàn)程的列表。 希望這樣解釋更詳細(xì)一些,如果還有疑問(wèn),請(qǐng)隨時(shí)問(wèn)我!??
ThreadLocal的數(shù)據(jù)結(jié)構(gòu)
GC 之后 key 是否為 null?
https://blog.csdn.net/thewindkee/article/details/103726942
ThreadLocal的內(nèi)存泄漏:
ThreadLocal的Hash算法
?
int i = key.threadLocalHashCode & (len-1);
ThreadLocalHash沖突
? 
ThreadLocalMap過(guò)期Key的探測(cè)式清理流程:
? 共有兩種過(guò)期Key的數(shù)據(jù)清理方式:探測(cè)式清理和啟發(fā)式清理
探測(cè)式清理:
? 離得更近,變位置。
啟發(fā)式清理:
?
ThreadLocal擴(kuò)容機(jī)制
在ThreadLocal.set()的最后,如果執(zhí)行完清理式工作后,未清理到任何數(shù)據(jù),且當(dāng)前散列數(shù)據(jù)中Entry的數(shù)量達(dá)到了列表的擴(kuò)容閾值(len*2/3),就開(kāi)始執(zhí)行rehash()邏輯。
rehash()的閾值為threshold*2/3
擴(kuò)容的閾值:只是通過(guò)
擴(kuò)容后的大小為oldLen*2.
Get方法
第一種情況:通過(guò)key值查找出散列表中slot的位置,然后通過(guò)判斷slot位置的key是否與查找的key值相同。相同則返回
第二種情況:slot位置的Entry.key和查找的key不一致:
? 不一致時(shí),往后迭代查找。當(dāng)Entry.key=null,觸發(fā)一次探測(cè)式數(shù)回收操作。
浙公網(wǎng)安備 33010602011771號(hào)