Java多線程與并發(fā)完整代碼詳解
一、多線程基礎(chǔ):優(yōu)缺點(diǎn)與核心代價(jià)
1. 核心優(yōu)點(diǎn)
資源利用率更高:CPU空閑時(shí)可調(diào)度其他線程執(zhí)行,避免硬件資源浪費(fèi)(如IO等待時(shí)CPU不閑置)。
程序設(shè)計(jì)更簡(jiǎn)潔:異步場(chǎng)景(如文件下載、接口調(diào)用)可通過(guò)多線程拆分任務(wù),簡(jiǎn)化復(fù)雜邏輯。
程序響應(yīng)更快:UI界面、服務(wù)端程序可通過(guò)多線程避免主線程阻塞,提升用戶/調(diào)用方體驗(yàn)。
2. 主要代價(jià)(易忽略細(xì)節(jié))
設(shè)計(jì)復(fù)雜度上升:需處理線程安全、同步、死鎖、線程通信等問(wèn)題,調(diào)試難度增加。
上下文切換開(kāi)銷:線程切換時(shí)需保存/恢復(fù)線程上下文(棧、寄存器等),消耗CPU資源(高頻切換會(huì)嚴(yán)重影響性能)。
額外資源消耗:每個(gè)線程需占用棧內(nèi)存(默認(rèn)1M左右)、內(nèi)核對(duì)象,過(guò)多線程會(huì)導(dǎo)致內(nèi)存溢出(OOM)。
二、線程創(chuàng)建與啟動(dòng)(關(guān)鍵細(xì)節(jié)補(bǔ)全)
1. 兩種標(biāo)準(zhǔn)實(shí)現(xiàn)方式
方式1:繼承Thread類
class MyThread extends Thread {
@Override
public void run() {
// 線程執(zhí)行的業(yè)務(wù)邏輯(線程體)
}
}
// 啟動(dòng)線程:調(diào)用start(),而非run()
new MyThread().start();
方式2:實(shí)現(xiàn)Runnable接口(推薦)
class MyTask implements Runnable {
@Override
public void run() {
// 線程執(zhí)行的業(yè)務(wù)邏輯(解耦:任務(wù)與線程分離)
}
}
// 啟動(dòng)線程:將任務(wù)傳入Thread實(shí)例
new Thread(new MyTask()).start();
2. 選型建議(筆記補(bǔ)充)
優(yōu)先選擇實(shí)現(xiàn)Runnable接口,原因如下:
解耦:任務(wù)邏輯(Runnable)與線程控制(Thread)分離,便于復(fù)用、維護(hù)。
適配高級(jí)特性:線程池、Future等JUC工具類僅支持Runnable接口,可降低后續(xù)擴(kuò)展成本。
規(guī)避單繼承限制:Java是單繼承語(yǔ)言,繼承Thread會(huì)占用唯一的繼承名額,影響類的擴(kuò)展性。
3. 經(jīng)典錯(cuò)誤(必記)
調(diào)用run()方法而非start():
直接調(diào)用run()只是普通方法調(diào)用(運(yùn)行在當(dāng)前線程),不會(huì)啟動(dòng)新線程;start()才會(huì)觸發(fā)JVM創(chuàng)建新線程,執(zhí)行run()方法。
三、線程安全核心:競(jìng)態(tài)條件與臨界區(qū)
1. 核心概念
競(jìng)態(tài)條件:多線程競(jìng)爭(zhēng)同一共享資源時(shí),執(zhí)行結(jié)果依賴線程執(zhí)行順序(如多線程自增同一個(gè)變量,結(jié)果可能小于預(yù)期值)。
臨界區(qū):導(dǎo)致競(jìng)態(tài)條件的代碼片段(即多個(gè)線程共享資源并可能修改資源的代碼塊)。
2. 本質(zhì)原因
共享資源的“讀-改-寫(xiě)”復(fù)合操作非原子性(如count++,實(shí)際分為3步:讀count值→自增→寫(xiě)回count,多線程交錯(cuò)執(zhí)行會(huì)導(dǎo)致數(shù)據(jù)錯(cuò)亂)。
四、線程安全判定:線程控制逃逸規(guī)則(重點(diǎn))
核心判定原則:若一個(gè)資源(對(duì)象、文件、數(shù)據(jù)庫(kù)連接等)的創(chuàng)建、使用、銷毀,全程在同一個(gè)線程內(nèi)完成,且不會(huì)逃逸到線程外部(即其他線程無(wú)法訪問(wèn)該資源),則該資源的使用是線程安全的。
1. 天生線程安全的場(chǎng)景
局部基本變量:存儲(chǔ)在線程私有棧中,不共享,天然安全(如方法內(nèi)的int、boolean變量)。
局部對(duì)象引用(無(wú)逃逸):對(duì)象本身在堆內(nèi)存(共享區(qū)域),但引用僅存在于當(dāng)前線程棧,且未通過(guò)返回值、參數(shù)傳遞等方式暴露給其他線程,線程安全。
2. 天生線程不安全的場(chǎng)景
對(duì)象成員變量(存儲(chǔ)在堆內(nèi)存,多線程可通過(guò)對(duì)象引用訪問(wèn)并修改,若未加同步,必存在線程安全問(wèn)題)。
五、不可變性與線程安全(補(bǔ)全細(xì)節(jié))
核心結(jié)論:只讀共享的資源不會(huì)產(chǎn)生線程安全問(wèn)題;不可變對(duì)象(狀態(tài)一旦創(chuàng)建無(wú)法修改)天然線程安全(如String、Integer等包裝類)。
關(guān)鍵提醒:不可變對(duì)象的引用不一定安全!例如:
AtomicReference<String> ref = new AtomicReference<>("a");,ref引用本身可被多線程修改(需用原子類保護(hù)),但引用指向的String對(duì)象本身不可變。
六、synchronized同步機(jī)制(核心,筆記細(xì)節(jié)補(bǔ)全)
synchronized是Java原生的悲觀鎖,通過(guò)“互斥”保證臨界區(qū)原子性,同時(shí)保證可見(jiàn)性(解鎖前的修改對(duì)后續(xù)加鎖線程可見(jiàn))和可重入性。
1. 四種作用范圍(附實(shí)例與鎖對(duì)象)
| 作用范圍 | 鎖對(duì)象 | 實(shí)例代碼 | 說(shuō)明 |
|---|---|---|---|
| 實(shí)例方法同步 | 當(dāng)前對(duì)象(this) | public synchronized void add(int value) { … } | 同一對(duì)象的多個(gè)同步實(shí)例方法,同一時(shí)刻僅一個(gè)線程可執(zhí)行 |
| 靜態(tài)方法同步 | 當(dāng)前類的Class對(duì)象(全局唯一) | public static synchronized void add(int value) { … } | 同一類的所有靜態(tài)同步方法,同一時(shí)刻僅一個(gè)線程可執(zhí)行 |
| 實(shí)例方法中同步塊 | 自定義對(duì)象(常用this) | synchronized(this) { this.count += value; } | 縮小同步范圍,僅保護(hù)臨界區(qū),提升性能 |
| 靜態(tài)方法中同步塊 | 當(dāng)前類的Class對(duì)象 | synchronized(MyClass.class) { … } | 與靜態(tài)同步方法鎖對(duì)象一致,可靈活控制同步范圍 |
2. 核心特性(必記)
可重入性:同一線程可重復(fù)獲取同一把鎖(如同步方法調(diào)用另一個(gè)同步方法,不會(huì)死鎖)。
互斥性:同一時(shí)刻,僅一個(gè)線程能持有鎖并進(jìn)入臨界區(qū)。
可見(jiàn)性:線程解鎖前,對(duì)共享變量的修改會(huì)強(qiáng)制刷新到主內(nèi)存,后續(xù)加鎖線程會(huì)從主內(nèi)存讀取最新值。
非公平性:線程喚醒后會(huì)隨機(jī)競(jìng)爭(zhēng)鎖,不保證請(qǐng)求順序(默認(rèn)非公平,性能更優(yōu))。
七、線程間通信(重點(diǎn):wait/notify/notifyAll)
線程間通信的核心是“協(xié)作”(如生產(chǎn)者-消費(fèi)者模型),常用方式:共享對(duì)象通信、忙等待、wait/notify/notifyAll(推薦)。
1. 共享對(duì)象通信(基礎(chǔ))
通過(guò)共享對(duì)象的成員變量傳遞信號(hào)(需配合同步,避免競(jìng)態(tài)條件)。例如:線程A在同步塊中設(shè)置hasDataToProcess = true,線程B在同步塊中讀取該變量。
2. 忙等待(不推薦)
線程B循環(huán)等待信號(hào),浪費(fèi)CPU資源(空閑時(shí)也占用CPU):
while(!sharedSignal.hasDataToProcess()){
// 空循環(huán),忙等待,浪費(fèi)CPU
}
3. wait()、notify()、notifyAll()(核心,補(bǔ)全細(xì)節(jié))
強(qiáng)制規(guī)則(違反拋IllegalMonitorStateException)
必須在synchronized同步塊/方法中調(diào)用,且調(diào)用對(duì)象必須是“當(dāng)前持有鎖的對(duì)象”(即同步塊的鎖對(duì)象)。
核心機(jī)制(易混淆點(diǎn))
wait():線程釋放持有的鎖,進(jìn)入該鎖的等待隊(duì)列,變?yōu)榉沁\(yùn)行狀態(tài),直到被notify()/notifyAll()喚醒。
notify():隨機(jī)喚醒等待隊(duì)列中的一個(gè)線程(喚醒后需重新競(jìng)爭(zhēng)鎖,才能進(jìn)入臨界區(qū))。
notifyAll():?jiǎn)拘训却?duì)列中的所有線程(所有線程競(jìng)爭(zhēng)鎖,依次進(jìn)入臨界區(qū))。
關(guān)鍵疑問(wèn)解答(筆記補(bǔ)充)
問(wèn):等待線程持有鎖,會(huì)阻塞喚醒線程進(jìn)入同步塊嗎?
答:不會(huì)。線程調(diào)用wait()后,會(huì)立即釋放鎖,允許其他線程(包括喚醒線程)獲取鎖并進(jìn)入同步塊;喚醒線程執(zhí)行完同步塊、釋放鎖后,被喚醒的線程才會(huì)競(jìng)爭(zhēng)鎖,成功后退出wait()。
4. 常見(jiàn)問(wèn)題與解決方案(必記)
| 問(wèn)題 | 原因 | 解決方案 |
|---|---|---|
| 丟失信號(hào) | notify()先于wait()執(zhí)行,信號(hào)未保存,等待線程錯(cuò)過(guò)喚醒 | 用成員變量保存信號(hào)(如boolean wasSignalled),喚醒時(shí)置為true,等待時(shí)檢查該變量 |
| 假喚醒 | 線程被喚醒但未收到有效信號(hào)(JVM底層機(jī)制) | 用while循環(huán)檢查信號(hào)(而非if),即“自旋鎖”:while(!wasSignalled) { wait(); } |
| 意外喚醒 | 用常量字符串、全局對(duì)象作為鎖,JVM會(huì)復(fù)用該對(duì)象,導(dǎo)致跨實(shí)例喚醒 | 使用唯一鎖對(duì)象(如new Object()),避免使用""、Class對(duì)象等全局共享對(duì)象 |
正確實(shí)現(xiàn)示例(避免所有問(wèn)題)
public class MyWaitNotify {
private final Object monitor = new Object(); // 唯一鎖對(duì)象
private boolean wasSignalled = false; // 保存信號(hào),避免丟失
// 等待信號(hào)
public void doWait() throws InterruptedException {
synchronized (monitor) {
while (!wasSignalled) { // while循環(huán),防止假喚醒
monitor.wait();
}
wasSignalled = false; // 清除信號(hào),準(zhǔn)備下次等待
}
}
// 發(fā)送信號(hào)
public void doNotify() {
synchronized (monitor) {
wasSignalled = true; // 保存信號(hào)
monitor.notify(); // 喚醒等待線程
}
}
}
八、死鎖(核心:原因與避免)
1. 定義
兩個(gè)或多個(gè)線程互相持有對(duì)方需要的鎖,且永久阻塞,無(wú)法繼續(xù)執(zhí)行(線程“互相僵持”)。
2. 典型場(chǎng)景(必記)
基礎(chǔ)場(chǎng)景:線程1鎖A→等B,線程2鎖B→等A,互相阻塞。
代碼場(chǎng)景(TreeNode示例):線程1調(diào)用parent.addChild(child)(鎖parent),線程2調(diào)用child.setParent(parent)(鎖child),后續(xù)線程1需鎖child、線程2需鎖parent,導(dǎo)致死鎖。
數(shù)據(jù)庫(kù)場(chǎng)景:多個(gè)事務(wù)更新相同記錄,順序相反(事務(wù)1鎖記錄1→等記錄2,事務(wù)2鎖記錄2→等記錄1)。
3. 死鎖產(chǎn)生的4個(gè)必要條件(缺一不可)
互斥:鎖資源只能被一個(gè)線程持有。
持有并等待:線程持有一個(gè)鎖,同時(shí)等待另一個(gè)鎖。
不可剝奪:線程持有鎖時(shí),不能被其他線程強(qiáng)制剝奪。
循環(huán)等待:多個(gè)線程形成“互相等待鎖”的循環(huán)鏈。
4. 避免死鎖的3種核心策略(重點(diǎn))
策略1:固定加鎖順序(最常用、最易實(shí)現(xiàn))
所有線程按相同順序獲取鎖(如先鎖A、再鎖B),打破“循環(huán)等待”條件。
策略2:加鎖時(shí)限
嘗試獲取鎖時(shí)設(shè)置超時(shí)時(shí)間(如用Lock.tryLock(long timeout, TimeUnit unit)),超時(shí)則釋放已持有鎖、回退,等待隨機(jī)時(shí)間后重試,打破“持有并等待”條件。
策略3:死鎖檢測(cè)
適用于無(wú)法固定加鎖順序、超時(shí)不可行的場(chǎng)景:
用數(shù)據(jù)結(jié)構(gòu)(如Map、有向圖)記錄線程持有/請(qǐng)求的鎖。
線程請(qǐng)求鎖失敗時(shí),遍歷鎖關(guān)系圖,檢測(cè)是否存在循環(huán)(死鎖)。
檢測(cè)到死鎖后,釋放所有鎖、回退重試,或按優(yōu)先級(jí)讓部分線程回退。
九、饑餓與公平性
1. 核心概念
饑餓:線程長(zhǎng)期得不到CPU運(yùn)行時(shí)間或鎖資源,最終“饑餓致死”(如低優(yōu)先級(jí)線程被高優(yōu)先級(jí)線程長(zhǎng)期搶占CPU)。
公平性:所有線程公平獲取資源(按請(qǐng)求順序獲?。?,避免饑餓。
2. Java中導(dǎo)致饑餓的3個(gè)原因
高優(yōu)先級(jí)線程吞噬低優(yōu)先級(jí)線程的CPU時(shí)間(Java優(yōu)先級(jí)1-10,優(yōu)先級(jí)高的線程被調(diào)度概率高)。
線程永久阻塞在同步塊外(非公平鎖下,新線程可能持續(xù)搶占鎖,老線程一直等待)。
線程等待一個(gè)永久無(wú)法完成的對(duì)象(如等待一個(gè)不會(huì)被notify()的對(duì)象)。
3. 實(shí)現(xiàn)公平性的方案
用Lock鎖替代synchronized(synchronized默認(rèn)非公平),自定義公平鎖或使用JUC的公平鎖(如ReentrantLock(true)):
公平鎖會(huì)維護(hù)一個(gè)等待隊(duì)列,解鎖后僅喚醒隊(duì)列頭部的線程,保證請(qǐng)求順序。
注意:公平鎖性能低于非公平鎖(需維護(hù)隊(duì)列、切換線程),僅在需要避免饑餓時(shí)使用。
十、Java中的鎖(進(jìn)階,補(bǔ)全筆記細(xì)節(jié))
1. 簡(jiǎn)單鎖的實(shí)現(xiàn)(理解原理)
核心是用“狀態(tài)變量+同步”控制線程訪問(wèn),示例(不可重入鎖):
public class Lock {
private boolean isLocked = false; // 鎖狀態(tài)
// 獲取鎖(阻塞)
public synchronized void lock() throws InterruptedException {
while (isLocked) { // 自旋,防止假喚醒
wait();
}
isLocked = true; // 標(biāo)記為鎖定狀態(tài)
}
// 釋放鎖
public synchronized void unlock() {
isLocked = false;
notify(); // 喚醒等待線程
}
}
2. 鎖的可重入性(重點(diǎn))
定義
同一線程可重復(fù)獲取已持有的鎖(synchronized、ReentrantLock均支持可重入),示例:
public class ReentrantDemo {
// 兩個(gè)同步方法,鎖對(duì)象都是this
public synchronized void outer() { inner(); }
public synchronized void inner() { /* 業(yè)務(wù)邏輯 */ }
}
可重入鎖的實(shí)現(xiàn)要點(diǎn)
需記錄“持有鎖的線程”和“重入次數(shù)”,修改后的可重入鎖示例:
public class ReentrantLock {
private boolean isLocked = false;
private Thread lockedBy = null; // 持有鎖的線程
private int lockedCount = 0; // 重入次數(shù)
public synchronized void lock() throws InterruptedException {
Thread currentThread = Thread.currentThread();
// 若鎖被占用,且不是當(dāng)前線程持有,阻塞
while (isLocked && lockedBy != currentThread) {
wait();
}
isLocked = true;
lockedCount++;
lockedBy = currentThread;
}
public synchronized void unlock() {
// 僅持有鎖的線程可釋放
if (Thread.currentThread() != lockedBy) {
throw new IllegalMonitorStateException("未持有鎖,無(wú)法釋放");
}
lockedCount--;
// 重入次數(shù)為0時(shí),才釋放鎖
if (lockedCount == 0) {
isLocked = false;
lockedBy = null;
notify();
}
}
}
3. 關(guān)鍵注意點(diǎn):finally中調(diào)用unlock()
用Lock鎖時(shí),臨界區(qū)可能拋出異常,需在finally中釋放鎖,避免鎖泄露(鎖永久被占用):
lock.lock();
try {
// 臨界區(qū)(可能拋出異常)
} finally {
lock.unlock(); // 確保無(wú)論是否異常,都釋放鎖
}
十一、讀寫(xiě)鎖(ReentrantReadWriteLock)
1. 核心場(chǎng)景
適用于“讀多寫(xiě)少”的場(chǎng)景(如緩存查詢、配置讀取),解決“讀-讀互斥”的性能問(wèn)題,核心原則:
讀-讀共存、讀-寫(xiě)互斥、寫(xiě)-寫(xiě)互斥。
2. 簡(jiǎn)單實(shí)現(xiàn)(理解原理)
public class ReadWriteLock {
private int readers = 0; // 讀線程數(shù)量
private int writers = 0; // 寫(xiě)線程數(shù)量
private int writeRequests = 0; // 寫(xiě)請(qǐng)求數(shù)量(優(yōu)先寫(xiě))
// 獲取讀鎖
public synchronized void lockRead() throws InterruptedException {
// 有寫(xiě)線程或?qū)懻?qǐng)求,阻塞(優(yōu)先寫(xiě))
while (writers > 0 || writeRequests > 0) {
wait();
}
readers++;
}
// 釋放讀鎖
public synchronized void unlockRead() {
readers--;
notifyAll(); // 喚醒等待的寫(xiě)線程
}
// 獲取寫(xiě)鎖
public synchronized void lockWrite() throws InterruptedException {
writeRequests++;
// 有讀線程或?qū)懢€程,阻塞
while (readers > 0 || writers > 0) {
wait();
}
writeRequests--;
writers++;
}
// 釋放寫(xiě)鎖
public synchronized void unlockWrite() {
writers--;
notifyAll(); // 喚醒所有等待的讀/寫(xiě)線程
}
}
3. 讀寫(xiě)鎖的可重入性(補(bǔ)全筆記)
上述簡(jiǎn)單實(shí)現(xiàn)不可重入,會(huì)導(dǎo)致死鎖(如持有寫(xiě)鎖的線程再次請(qǐng)求寫(xiě)鎖、持有讀鎖的線程再次請(qǐng)求讀鎖),需優(yōu)化:
讀鎖重入:用Map記錄線程及讀鎖重入次數(shù),已持有讀鎖的線程可再次獲取讀鎖(無(wú)論是否有寫(xiě)請(qǐng)求)。
寫(xiě)鎖重入:記錄持有寫(xiě)鎖的線程及重入次數(shù),已持有寫(xiě)鎖的線程可再次獲取寫(xiě)鎖。
鎖降級(jí):持有寫(xiě)鎖的線程可直接獲取讀鎖(無(wú)需釋放寫(xiě)鎖),避免寫(xiě)鎖釋放后被其他線程搶占。
十二、信號(hào)量(Semaphore)
1. 核心作用
控制并發(fā)線程數(shù)量(如限流、連接池控制),可看作“可計(jì)數(shù)的鎖”,JUC中已提供java.util.concurrent.Semaphore,無(wú)需自定義。
2. 核心特性
可計(jì)數(shù):允許同時(shí)獲取多個(gè)許可(如permits=5,可同時(shí)有5個(gè)線程獲取許可)。
有上限:可設(shè)置最大許可數(shù)(如上限10,超過(guò)則阻塞)。
可當(dāng)作鎖:當(dāng)permits=1時(shí),等價(jià)于非公平鎖(控制單個(gè)線程進(jìn)入臨界區(qū))。
3. 常用示例(限流)
// 上限3個(gè)線程同時(shí)執(zhí)行
Semaphore semaphore = new Semaphore(3);
for (int i = 0; i < 10; i++) {
new Thread(() -> {
try {
semaphore.acquire(); // 獲取許可(阻塞,直到有空閑許可)
// 臨界區(qū)(如接口調(diào)用、資源操作)
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release(); // 釋放許可
}
}).start();
}
十三、阻塞隊(duì)列(BlockingQueue)
1. 核心區(qū)別(與普通隊(duì)列)
當(dāng)隊(duì)列空時(shí),獲取元素(take())會(huì)阻塞;當(dāng)隊(duì)列滿時(shí),添加元素(put())會(huì)阻塞,無(wú)需手動(dòng)處理線程同步(內(nèi)部已實(shí)現(xiàn))。
2. 核心作用
實(shí)現(xiàn)生產(chǎn)者-消費(fèi)者模型(解耦生產(chǎn)者和消費(fèi)者,平衡兩者速度),線程池底層核心組件(緩存任務(wù))。
3. 簡(jiǎn)單實(shí)現(xiàn)(理解原理)
public class BlockingQueue {
private final List<Object> queue = new LinkedList<>();
private final int limit; // 隊(duì)列最大容量
public BlockingQueue(int limit) {
this.limit = limit;
}
// 入隊(duì)(滿則阻塞)
public synchronized void enqueue(Object item) throws InterruptedException {
while (queue.size() == limit) {
wait(); // 隊(duì)列滿,阻塞生產(chǎn)者
}
if (queue.size() == 0) {
notifyAll(); // 隊(duì)列空,喚醒消費(fèi)者
}
queue.add(item);
}
// 出隊(duì)(空則阻塞)
public synchronized Object dequeue() throws InterruptedException {
while (queue.size() == 0) {
wait(); // 隊(duì)列空,阻塞消費(fèi)者
}
if (queue.size() == limit) {
notifyAll(); // 隊(duì)列滿,喚醒生產(chǎn)者
}
return queue.remove(0);
}
}
4. JUC中的阻塞隊(duì)列(常用)
如ArrayBlockingQueue(有界)、LinkedBlockingQueue(無(wú)界/有界)、SynchronousQueue(無(wú)緩沖),直接使用即可,無(wú)需自定義。
十四、線程池(核心,補(bǔ)全筆記細(xì)節(jié))
1. 核心價(jià)值
避免頻繁創(chuàng)建/銷毀線程的性能開(kāi)銷(線程創(chuàng)建需分配棧內(nèi)存、內(nèi)核對(duì)象)。
控制并發(fā)線程數(shù)量,防止線程過(guò)多導(dǎo)致OOM或CPU耗盡。
統(tǒng)一管理線程,便于監(jiān)控、調(diào)度、復(fù)用(如任務(wù)緩存、拒絕策略)。
2. 核心結(jié)構(gòu)
工作線程集合:長(zhǎng)期存活,循環(huán)從隊(duì)列中獲取任務(wù)執(zhí)行。
阻塞隊(duì)列:緩存等待執(zhí)行的任務(wù)(如生產(chǎn)者-消費(fèi)者模型中的隊(duì)列)。
拒絕策略:隊(duì)列滿且線程數(shù)達(dá)到最大時(shí),處理新任務(wù)的策略(如拋異常、丟棄、阻塞)。
3. 簡(jiǎn)單實(shí)現(xiàn)(理解原理)
// 線程池核心類
public class ThreadPool {
private final BlockingQueue<Runnable> taskQueue; // 任務(wù)隊(duì)列
private final List<PoolThread> threads; // 工作線程集合
private boolean isStopped = false; // 線程池狀態(tài)
// 構(gòu)造方法:指定線程數(shù)、隊(duì)列最大容量
public ThreadPool(int threadCount, int maxTaskCount) {
taskQueue = new BlockingQueue<>(maxTaskCount);
threads = new ArrayList<>(threadCount);
// 初始化工作線程
for (int i = 0; i < threadCount; i++) {
threads.add(new PoolThread(taskQueue));
}
// 啟動(dòng)所有工作線程
for (PoolThread thread : threads) {
thread.start();
}
}
// 提交任務(wù)
public synchronized void execute(Runnable task) {
if (isStopped) {
throw new IllegalStateException("線程池已停止");
}
taskQueue.enqueue(task); // 任務(wù)入隊(duì)
}
// 停止線程池
public synchronized void stop() {
isStopped = true;
// 停止所有工作線程
for (PoolThread thread : threads) {
thread.toStop();
}
}
}
// 工作線程類
class PoolThread extends Thread {
private final BlockingQueue<Runnable> taskQueue;
private boolean isStopped = false;
public PoolThread(BlockingQueue<Runnable> queue) {
this.taskQueue = queue;
}
@Override
public void run() {
// 循環(huán)獲取任務(wù),直到線程池停止
while (!isStopped()) {
try {
// 從隊(duì)列獲取任務(wù)(空則阻塞)
Runnable task = taskQueue.dequeue();
task.run(); // 執(zhí)行任務(wù)
} catch (Exception e) {
// 捕獲異常,避免線程退出(線程池繼續(xù)運(yùn)行)
e.printStackTrace();
}
}
}
// 停止當(dāng)前工作線程
public synchronized void toStop() {
isStopped = true;
this.interrupt(); // 打斷阻塞在dequeue()的線程
}
public synchronized boolean isStopped() {
return isStopped;
}
}
4. JUC中的線程池(重點(diǎn))
使用java.util.concurrent.ExecutorService,推薦手動(dòng)創(chuàng)建ThreadPoolExecutor(避免Executors工具類的OOM風(fēng)險(xiǎn)),核心參數(shù):核心線程數(shù)、最大線程數(shù)、空閑線程存活時(shí)間、任務(wù)隊(duì)列、拒絕策略。
十五、CAS與原子類(無(wú)鎖編程核心)
1. CAS核心概念
CAS(Compare and Swap,比較并替換):一種無(wú)鎖原子操作,底層由CPU指令(如cmpxchg)保證原子性,核心邏輯:
傳入3個(gè)參數(shù):內(nèi)存地址V、期望值A(chǔ)、新值B。
比較V的值與A:若相等,將V的值替換為B;若不相等,不做操作。
返回操作結(jié)果(是否替換成功)。
2. 核心優(yōu)點(diǎn)
無(wú)鎖、無(wú)線程上下文切換開(kāi)銷、無(wú)死鎖風(fēng)險(xiǎn),性能優(yōu)于synchronized(高并發(fā)、低沖突場(chǎng)景)。
3. 核心問(wèn)題與解決方案
| 問(wèn)題 | 原因 | 解決方案 |
|---|---|---|
| ABA問(wèn)題 | V的值從A→B→A,CAS認(rèn)為未修改,導(dǎo)致錯(cuò)誤替換 | 用AtomicStampedReference(加版本號(hào)) |
| 自旋消耗CPU | 高并發(fā)下,CAS多次失敗,循環(huán)重試消耗CPU | 限制自旋次數(shù),或搭配鎖使用 |
| 只能保證單個(gè)變量原子性 | CAS僅能操作單個(gè)變量,無(wú)法保證多個(gè)變量的復(fù)合操作原子性 | 用AtomicReference包裝多個(gè)變量,或使用鎖 |
4. JUC原子類(常用)
基本類型:AtomicInteger、AtomicLong、AtomicBoolean(如計(jì)數(shù)器、狀態(tài)標(biāo)記)。
引用類型:AtomicReference、AtomicStampedReference(解決ABA問(wèn)題)。
數(shù)組類型:AtomicIntegerArray、AtomicLongArray(原子操作數(shù)組元素)。
十六、同步器核心思想(查漏補(bǔ)缺)
鎖、信號(hào)量、阻塞隊(duì)列等同步器,底層設(shè)計(jì)邏輯一致,均包含4個(gè)核心部分:
狀態(tài):控制線程訪問(wèn)權(quán)限(如Lock的isLocked、Semaphore的permits)。
訪問(wèn)條件:基于狀態(tài)判斷線程是否可訪問(wèn)(如while循環(huán)檢查,防止假喚醒)。
狀態(tài)變化:線程獲取/釋放資源時(shí),修改同步器狀態(tài)(如Lock的isLocked=true/false)。
通知策略:狀態(tài)變化后,通知等待線程(notify()/notifyAll(),如釋放鎖后喚醒等待線程)。
同步器的兩種核心方法
Test-and-Set(測(cè)試-設(shè)置):原子操作,先檢查訪問(wèn)條件,滿足則修改狀態(tài)(如CAS、lock())。
Set(設(shè)置):僅修改狀態(tài),不檢查條件(如unlock(),持有鎖的線程可直接釋放)。
十七、阻塞算法與非阻塞算法(補(bǔ)全筆記)
1. 核心區(qū)別
| 類型 | 核心邏輯 | 優(yōu)點(diǎn) | 缺點(diǎn) | 示例 |
|---|---|---|---|---|
| 阻塞算法 | 獲取不到資源時(shí),線程掛起,直到資源可用 | 實(shí)現(xiàn)簡(jiǎn)單,無(wú)CPU自旋消耗 | 線程切換開(kāi)銷大,可能死鎖 | synchronized、Lock |
| 非阻塞算法 | 獲取不到資源時(shí),不掛起,直接返回或重試 | 無(wú)線程切換開(kāi)銷,無(wú)死鎖 | 實(shí)現(xiàn)復(fù)雜,高沖突下自旋消耗CPU | CAS、原子類 |
2. 樂(lè)觀鎖(非阻塞算法核心)
核心思想:樂(lè)觀假設(shè)“無(wú)并發(fā)沖突”,線程先拷貝共享資源、修改,再通過(guò)CAS將修改寫(xiě)回主內(nèi)存,沖突則重試。
適用場(chǎng)景:低并發(fā)、低沖突(如緩存更新),避免鎖的開(kāi)銷。
注意:高沖突場(chǎng)景下,重試頻繁,性能低于阻塞算法。
十八、高頻面試重點(diǎn)(濃縮必記)
線程啟動(dòng)必須用start(),調(diào)用run()僅為普通方法調(diào)用,不啟動(dòng)新線程。
線程安全三要素:原子性(CAS、鎖)、可見(jiàn)性(synchronized、volatile)、有序性(synchronized、volatile)。
synchronized與Lock的區(qū)別:Lock可中斷、可超時(shí)、可公平/非公平,synchronized自動(dòng)釋放鎖、可重入、簡(jiǎn)單易用。
死鎖產(chǎn)生的4個(gè)條件及避免方法(固定加鎖順序最常用)。
CAS的原理、優(yōu)點(diǎn)、ABA問(wèn)題及解決方案。
線程池的核心價(jià)值、結(jié)構(gòu),手動(dòng)創(chuàng)建ThreadPoolExecutor的原因(避免Executors的OOM)。
讀寫(xiě)鎖的核心原則(讀-讀共存、讀-寫(xiě)/寫(xiě)-寫(xiě)互斥),適用場(chǎng)景(讀多寫(xiě)少)。
wait()與sleep()的區(qū)別:wait()釋放鎖、需在同步塊中,sleep()不釋放鎖、可在任意位置。
總結(jié)
到此這篇關(guān)于Java多線程與并發(fā)的文章就介紹到這了,更多相關(guān)Java多線程與并發(fā)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
springboot配置允許跨域訪問(wèn)代碼實(shí)例
這篇文章主要介紹了springboot配置允許跨域訪問(wèn)代碼實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-09-09
spring boot啟動(dòng)時(shí)mybatis報(bào)循環(huán)依賴的錯(cuò)誤(推薦)
今天小編抽時(shí)間給大家分享spring boot啟動(dòng)時(shí)mybatis報(bào)循環(huán)依賴的錯(cuò)誤,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友參考下吧2017-12-12
eclipse/intellij idea 查看java源碼和注釋方法
下面小編就為大家?guī)?lái)一篇eclipse/intellij idea 查看java源碼和注釋方法。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-05-05
Spring?Boot在開(kāi)發(fā)過(guò)程中常用IDEA插件
這篇文章主要為大家介紹了Spring?Boot在開(kāi)發(fā)過(guò)程中常用IDEA插件,幫助大家提高開(kāi)發(fā)工作效率,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步2022-03-03
Java Swing界面美化與JPanel優(yōu)化的完全指南
ava Swing 作為經(jīng)典的桌面應(yīng)用框架,功能強(qiáng)大但界面設(shè)計(jì)往往停留在上個(gè)時(shí)代,本文系統(tǒng)介紹如何使用 FlatLaf 等現(xiàn)代化工具美化 Swing 界面,并提供 JPanel 布局優(yōu)化的完整解決方案,讓你的桌面應(yīng)用煥然一新,需要的朋友可以參考下2026-02-02
windows系統(tǒng)上如何進(jìn)行maven安裝和配置方式
這篇文章主要介紹了windows系統(tǒng)上如何進(jìn)行maven安裝和配置方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2025-05-05
Java從控制臺(tái)接受輸入字符的簡(jiǎn)單方法
這篇文章主要介紹了Java從控制臺(tái)接受輸入字符的簡(jiǎn)單方法,需要的朋友可以參考下2014-02-02

