Java多線程:核心技術與實戰指南

目錄

  • 🚀前言
  • 🤔什么是多線程?
  • 💻創建線程
    • 💯創建方法一:繼承Thread類
    • 💯創建方法二:實現Runnable接口
    • 💯創建方法三:實現Callable接口
    • 💯三種方法對比
  • 🦜Thread的常用方法
  • ??線程安全與線程同步
    • 💯先搞懂:什么是線程安全?(附比喻)
    • 💯線程同步:給多線程定“排隊規則”
      • 🎯方式1:同步代碼塊(synchronized塊)
      • 🎯方式2:同步方法(synchronized方法)
      • 🎯方式3:Lock鎖(顯式鎖)
    • 💯三種方式對比總結
    • 💯同步的“代價”
  • 🌟線程池
    • 💯認識線程池
    • 💯創建線程池
    • 💯處理Runnable任務
    • 💯處理Callable任務
    • 💯通過Executors創建線程池
    • 💯線程數配置公式
  • ??并發與并行
    • 🌰 并發執行(單核CPU場景)
    • 🌰 并行執行(多核CPU場景)

🚀前言

在這里插入圖片描述

大家好!我是 EnigmaCoder

  • 在Java編程中,“多線程”是一個高頻出現的概念,也是處理并發任務的核心技術。如果你想理解程序如何“同時”處理多個任務(比如一邊下載文件一邊刷新界面),那多線程就是繞不開的知識點。今天我們就從多線程的定義、創建線程、線程安全與同步、線程池等多個維度,聊聊Java中的多線程。

🤔什么是多線程?

定義:多線程(Multithreading)是指在一個程序(進程)中,同時運行多個獨立的執行單元(線程),這些線程共享程序的內存資源(如變量、方法),但擁有各自的執行路徑。

  • 簡單說,線程是進程(一個正在運行的程序,比如你的Java程序、瀏覽器)中的“小任務”。一個進程至少有一個線程(稱為“主線程”),而多線程就是給一個進程“拆分”出多個并行的小任務,讓它們協同完成工作。
  • 舉個生活例子:進程像一家餐廳,主線程是餐廳的“基礎運營”(開門、開燈);多線程就像餐廳里同時工作的服務員、廚師、收銀員——他們共享餐廳的資源(食材、餐具),但各自執行不同的任務,最終共同完成“服務顧客”的目標。

要理解多線程的價值,先得搞清楚它和“多進程”的區別

  • 多進程:多個獨立的程序同時運行(比如同時開著微信和瀏覽器),進程間內存不共享,通信成本高(需要通過網絡或文件等方式)。
  • 多線程:同一個程序內的多個任務,共享內存(變量、對象等),通信成本低,且創建/切換線程的資源消耗遠低于進程。

優點

  1. 避免阻塞,提升用戶體驗
    比如一個Java桌面程序,如果用單線程(只有主線程),當執行一個耗時操作(如下載大文件)時,主線程會被“卡住”,界面會變成“無響應”狀態。而多線程可以把下載任務交給“子線程”,主線程繼續處理界面刷新,用戶完全感知不到卡頓。

  2. 利用多核CPU,提高效率
    現代CPU都是多核的,單線程只能用一個核心,多線程可以讓不同線程跑在不同核心上,真正實現“并行計算”。比如處理大量數據時,多線程拆分任務后,效率可能提升數倍。

  3. 簡化復雜任務的拆分
    有些任務天然適合拆分(比如同時處理100個用戶的請求),多線程可以讓每個請求對應一個線程,邏輯更清晰,無需手動協調任務順序。

缺點

  • 線程安全問題:多個線程共享資源時,可能出現“搶資源”的情況。比如兩個線程同時修改一個變量,可能導致結果錯亂(專業稱“競態條件”)。
  • 復雜度提升:需要處理線程間的協調(如等待、喚醒),調試難度也更高(線程執行順序不確定)。

💻創建線程

💯創建方法一:繼承Thread類

實現步驟

  1. 創建自定義線程類:繼承java.lang.Thread類,并重寫run()方法
  2. 實例化線程:創建該自定義線程類的對象
  3. 啟動線程:調用線程對象的start()方法

代碼示例

public class ThreadDemo1 {public static void main(String[] args) {Thread t1 =new MyThread();t1.start();for(int i=0;i<5;i++){System.out.println("主線程輸出:"+i);}}
}
class MyThread extends Thread {@Overridepublic void run (){for(int i=0;i<5;i++){System.out.println("子線程輸出:"+i);}}
}

注意事項

  • 調用start()方法后,JVM會自動執行run()方法中的邏輯
  • 只有調用start()方法才是啟動一個新的線程執行,直接調用run()方法將不會創建新線程,此時相當于還是單線程執行。
  • 不要把主線程任務放在子線程之前,否則一定是主線程先跑完再跑完子線程。

優缺點:

  • 優點:編碼簡單。
  • 缺點:線程類已經繼承Thread,無法繼承其他類,不利于功能的擴展。

💯創建方法二:實現Runnable接口

實現步驟

  1. 創建自定義線程類MyRunnable,實現Runnable接口并重寫其run()方法
  2. 實例化MyRunnable任務對象
  3. 將任務對象作為參數傳遞給Thread類構造函數
  4. 調用Thread實例的start()方法啟動新線程

代碼示例

public class ThreadDemo2 {public static void main(String[] args) {Runnable r =new MyRunnable();Thread t1 = new Thread(r);t1.start();for(int i=0;i<5;i++){System.out.println("主線程輸出:"+i);}}
}
class MyRunnable implements Runnable {@Overridepublic void run (){for(int i=0;i<5;i++){System.out.println("子線程輸出:"+i);}}
}

優缺點:

  • 優點:任務類只是實現接口,可以繼續繼承其他類,實現其他接口,擴展性強。
  • 缺點:需要多一個Runnable對象。

💯創建方法三:實現Callable接口

實現步驟

  1. 創建任務對象:通過定義一個實現Callable接口的類,重寫其call方法來封裝業務邏輯和返回數據。然后將Callable對象包裝成FutureTask線程任務對象。

  2. 提交任務:將創建的FutureTask對象傳遞給Thread對象進行執行。

  3. 啟動線程:調用Thread對象的start方法啟動線程執行任務。

  4. 獲取結果:待線程執行完成后,通過調用FutureTaskget方法獲取任務執行結果。

代碼示例

public class Test {public static void main(String[] args) {Callable<String> c1=new MyCallable(100);FutureTask<String> f1 =new FutureTask<>(c1);Thread t1 = new Thread(f1);t1.start();Callable<String> c2=new MyCallable(50);FutureTask<String> f2 =new FutureTask<>(c2);Thread t2 = new Thread(f2);t2.start();try {System.out.println(f1.get());} catch (Exception e) {e.printStackTrace();}try {System.out.println(f2.get());}catch(Exception e){e.printStackTrace();}}
}class MyCallable implements Callable<String> {private int n;public MyCallable(int n){this.n=n;}public String call() throws Exception{int sum=0;for(int i=1;i<=n;i++){sum+=i;}return "子線程計算1-"+n+"的和是:"+sum;}}

注意事項

  • 如果主線程發現某個線程還沒有執行完畢,會讓出CPU,等這個線程執行完畢后,再向下執行。

FutureTask的API

FutureTask提供的構造器說明
public FutureTask<>(Callable call)把Callable對象封裝成FutureTask對象
FutureTask提供的方法說明
public V get() throws Exception 獲取線程執行call方法返回的結果

優缺點:

  • 優點:線程任務類只是實現接口,可以繼續繼承類和實現接口,擴展性強。可以在線程執行完畢后獲取線程執行的結果。
  • 缺點:編碼會更加復雜。

💯三種方法對比

方式優點缺點
繼承Thread類編程比較簡單,可以直接使用Thread類中的方法擴展性較差,不能再繼承其他的類,不能返回線程執行的結果
實現Runnable接口擴展性強,實現該接口的同時還可以繼承其他的類編程相對復雜,不能返回線程執行的結果
實現Callable接口擴展性強,實現該接口的同時還可以繼承其他的類。可以得到線程執行的結果編程相對復雜

🦜Thread的常用方法

  1. Thread 常用方法
方法簽名說明
public void run()線程執行的任務邏輯(需重寫,定義線程要做的事)
public void start()啟動線程(JVM 會自動調用 run 方法,真正開啟新線程執行)
public String getName()獲取線程名稱(默認格式:Thread-索引,如 Thread-0
public void setName(String name)設置線程名稱(自定義線程標識)
public static Thread currentThread()獲取當前執行的線程對象(區分主線程/子線程)
public static void sleep(long time)當前線程休眠 time 毫秒(休眠后自動繼續執行)
public final void join()當前線程等待調用者執行完畢(如主線程等子線程,需處理中斷異常)
  1. Thread 常見構造器
構造器簽名說明
public Thread(String name)直接創建線程并指定名稱(適合繼承 Thread 類的場景)
public Thread(Runnable target)封裝 Runnable 對象為線程(解耦任務與線程,推薦使用)
public Thread(Runnable target, String name)封裝 Runnable 對象并指定線程名稱(靈活場景)
  1. 綜合示例:Thread 方法+構造器全場景演示
// 1. 定義 Runnable 任務(解耦線程邏輯)
class MyRunnable implements Runnable {@Overridepublic void run() {// 獲取當前線程信息Thread current = Thread.currentThread();System.out.println(current.getName() + " 執行 Runnable 任務");try {// 休眠 2 秒(模擬耗時操作)Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(current.getName() + " 休眠結束,任務完成");}
}// 2. 繼承 Thread 類(直接定義線程邏輯)
class MyThread extends Thread {public MyThread(String name) {super(name); // 調用父類構造器設置線程名稱}@Overridepublic void run() {Thread current = Thread.currentThread();System.out.println(current.getName() + " 執行 Thread 子類任務");try {Thread.sleep(1000); // 休眠 1 秒} catch (InterruptedException e) {e.printStackTrace();}System.out.println(current.getName() + " 休眠結束");}
}// 3. 主類:演示所有方法+構造器
public class ThreadDemo {public static void main(String[] args) {// ========== 構造器 1:Thread(String name) —— 繼承 Thread 類 ==========MyThread thread1 = new MyThread("子類線程");// ========== 構造器 2:Thread(Runnable target) —— 封裝 Runnable ==========Thread thread2 = new Thread(new MyRunnable()); // 名稱默認:Thread-1// ========== 構造器 3:Thread(Runnable target, String name) —— 自定義名稱 ==========Thread thread3 = new Thread(new MyRunnable(), "命名任務線程");// ========== 啟動線程(必須用 start(),直接調 run() 是普通方法!) ==========System.out.println("=== 啟動線程 ===");thread1.start();thread2.start();thread3.start();// ========== 主線程操作:getName()/setName() ==========Thread mainThread = Thread.currentThread();System.out.println("主線程原名稱:" + mainThread.getName()); // 默認:mainmainThread.setName("自定義主線程");System.out.println("主線程新名稱:" + mainThread.getName());// ========== 演示 join():主線程等待 thread1 完成 ==========try {System.out.println("主線程等待「子類線程」完成...");thread1.join(); // 主線程進入等待System.out.println("「子類線程」已完成,主線程繼續");} catch (InterruptedException e) {e.printStackTrace();}// ========== 演示 sleep():主線程休眠 3 秒 ==========try {System.out.println("主線程開始休眠 3 秒");Thread.sleep(3000);System.out.println("主線程休眠結束");} catch (InterruptedException e) {e.printStackTrace();}// ========== 對比:直接調用 run()(非線程啟動!) ==========System.out.println("=== 直接調用 run()(無新線程)===");MyRunnable runnable = new MyRunnable();runnable.run(); // 在主線程中執行,不會開啟新線程}
}
  1. 示例運行效果 :
  • 線程啟動
    • thread1(子類線程)、thread2(默認名)、thread3(命名任務線程)通過 start() 啟動,各自開啟新線程執行 run()
  • 方法調用
    • getName()/setName():主線程名稱從 main 改為 自定義主線程
    • join():主線程等待 thread1 完成后再繼續。
    • sleep():主線程休眠 3 秒,模擬耗時操作。
  • 關鍵對比
    • start() 啟動新線程,run() 直接調用僅為普通方法(無新線程)。
  1. 小結
場景正確用法常見誤區
啟動線程thread.start()直接調用 thread.run()(無新線程)
定義線程邏輯實現 Runnable(解耦優先)過度使用繼承 Thread(單繼承限制)
線程命名構造器指定或 setName()依賴默認名稱(不利于調試)
線程等待thread.join()忽略中斷異常處理

??線程安全與線程同步

上面我們聊了多線程的基礎,知道它能讓程序“一心多用”。但如果多個線程同時搶著用同一個資源,就可能出亂子——這就是“線程安全”問題。而“線程同步”就是給多線程定規矩,讓它們有序訪問資源。下面咱們用生活化的例子,聊聊這兩個概念和三種同步方式。

💯先搞懂:什么是線程安全?(附比喻)

線程安全:多個線程同時操作共享資源時,無論線程執行順序如何,最終結果都和“單線程執行”的結果一致,就叫線程安全。反之,結果錯亂就是“線程不安全”。

舉個最直觀的例子

假設你和3個朋友(4個線程)一起搶10張演唱會門票(共享資源),每個人都在同時喊“我要1張”。如果沒有規則,可能出現:

  • 最后統計時,明明只有10張票,卻被搶走了11張(超賣);
  • 或者有人喊了“要1張”,但票沒減少(漏賣)。

這就是典型的“線程不安全”——共享資源被多線程亂搶,結果錯亂。

為什么會這樣?
因為線程操作資源的過程(比如“判斷剩余票數→減少1張”)不是“一步完成”的,而是分成幾步(讀、改、寫)。多個線程可能在“讀”之后、“寫”之前插隊,導致數據錯亂。

💯線程同步:給多線程定“排隊規則”

線程同步的核心是:讓多個線程“有序訪問”共享資源,避免同時操作。就像給搶票的人定規則:“每次只能一個人去查票、買票,其他人排隊等”。

同步的本質是“加鎖”:把共享資源的操作過程“鎖住”,一個線程操作時,其他線程必須等它完成并“解鎖”后才能繼續。

接下來講三種最常用的同步方式

🎯方式1:同步代碼塊(synchronized塊)

定義:用synchronized(鎖對象)包裹需要同步的代碼,只有拿到“鎖對象”的線程才能執行塊內代碼,執行完自動釋放鎖。

synchronized(同步鎖){訪問共享資源的核心代碼
}

比喻
就像食堂打飯,勺子(鎖對象)只有一個。大家想打飯(執行代碼塊),必須先拿到勺子(獲得鎖),打完飯(執行完)把勺子放回(釋放鎖),下一個人才能拿。

代碼示例:解決搶票問題

public class Ticket implements Runnable {private int ticketCount = 10; // 共享的10張票private Object lock = new Object(); // 鎖對象(任意對象都可)@Overridepublic void run() {while (true) {// 同步代碼塊:鎖住"查票+賣票"的核心操作synchronized (lock) { if (ticketCount > 0) {// 模擬網絡延遲(放大線程安全問題)try { Thread.sleep(100); } catch (InterruptedException e) {}System.out.println(Thread.currentThread().getName() + "賣出1張,剩余:" + (--ticketCount));} else {break;}}}}public static void main(String[] args) {Ticket ticket = new Ticket();// 4個線程(4個人)搶票new Thread(ticket, "線程1").start();new Thread(ticket, "線程2").start();new Thread(ticket, "線程3").start();new Thread(ticket, "線程4").start();}
}

說明

  • lock是鎖對象,必須是多個線程“共享”的同一個對象(否則鎖不住)。
  • 同步代碼塊只鎖“必要的代碼”(查票+賣票),范圍越小,效率越高(別把整個循環鎖住,不然和單線程沒區別)。

注意事項

  • 對于實例方法建議使用this作為鎖對象。
  • 對于靜態方法建議使用字節碼(類名.class )對象作為鎖對象。

🎯方式2:同步方法(synchronized方法)

定義:在方法聲明處加synchronized關鍵字,整個方法成為同步方法。

  • 非靜態同步方法:鎖對象是this(當前對象)。
  • 靜態同步方法:鎖對象是當前類的Class對象(類名.class)。
修飾符 synchronized 返回值類型 方法名稱(形參列表){操作共享資源的代碼
}

比喻
就像公共電話亭,電話亭(同步方法)本身就是“鎖”。一個人進去打電話(執行方法),會把門反鎖(獲得鎖),打完電話出來(方法結束)才開鎖,下一個人才能進。

代碼示例:用同步方法解決搶票問題

public class Ticket implements Runnable {private int ticketCount = 10;// 同步方法:整個方法被鎖住,鎖對象是this(當前Ticket對象)private synchronized void sellTicket() {if (ticketCount > 0) {try { Thread.sleep(100); } catch (InterruptedException e) {}System.out.println(Thread.currentThread().getName() + "賣出1張,剩余:" + (--ticketCount));}}@Overridepublic void run() {while (ticketCount > 0) {sellTicket(); // 調用同步方法}}public static void main(String[] args) {Ticket ticket = new Ticket();new Thread(ticket, "線程1").start();new Thread(ticket, "線程2").start();new Thread(ticket, "線程3").start();new Thread(ticket, "線程4").start();}
}

說明
同步方法比同步代碼塊更簡潔,直接把整個方法設為同步。但要注意:如果方法里有不需要同步的代碼,會降低效率(相當于整個電話亭都排隊,哪怕只是進去拿個東西)。

🎯方式3:Lock鎖(顯式鎖)

定義:JDK 5后新增的java.util.concurrent.locks.Lock接口(常用實現類ReentrantLock),需手動調用lock()加鎖、unlock()釋放鎖,更靈活。

比喻
就像租共享單車,你需要手動掃碼開鎖(lock()),用完后手動關鎖(unlock())。比同步代碼塊/方法更靈活(比如可以中途解鎖)。

代碼示例:用Lock解決搶票問題

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class Ticket implements Runnable {private int ticketCount = 10;// 創建Lock鎖對象private  Lock lock = new ReentrantLock();@Overridepublic void run() {while (true) {lock.lock(); // 加鎖try {if (ticketCount > 0) {try { Thread.sleep(100); } catch (InterruptedException e) {}System.out.println(Thread.currentThread().getName() + "賣出1張,剩余:" + (--ticketCount));} else {break;}} finally {lock.unlock(); // 釋放鎖(必須放finally里,確保一定釋放)}}}public static void main(String[] args) {Ticket ticket = new Ticket();new Thread(ticket, "線程1").start();new Thread(ticket, "線程2").start();new Thread(ticket, "線程3").start();new Thread(ticket, "線程4").start();}
}

說明

  • lock()unlock()必須成對出現,unlock()finally里,避免線程異常時鎖沒釋放,導致死鎖。
  • synchronized更靈活:支持嘗試獲取鎖(tryLock())、可中斷鎖等,適合復雜場景。

💯三種方式對比總結

方式語法鎖釋放靈活性適用場景
同步代碼塊synchronized(鎖對象)自動釋放中等(指定鎖)部分代碼需要同步時
同步方法synchronized修飾方法自動釋放低(鎖固定)整個方法需要同步時
Lock鎖lock()+unlock()手動釋放高(靈活控制)復雜同步場景(如嘗試鎖、超時)

💯同步的“代價”

同步能解決線程安全問題,但也有成本:

  • 線程需要排隊等待鎖,會降低并發效率(就像大家都排隊打飯,速度肯定比各打各的慢)。
  • 過度同步可能導致“死鎖”(比如線程A拿著鎖1等鎖2,線程B拿著鎖2等鎖1,互相卡死)。

所以,同步不是“越多越好”,而是“按需使用”:只給真正需要同步的代碼加鎖,平衡安全性和效率。

🌟線程池

在多線程編程里,線程池 是提升效率的關鍵武器。它像一家“勞務公司”——提前養一批線程(工人)待命,任務(活)來了直接分配,避免頻繁招人(創建線程)、裁人(銷毀線程)的資源浪費。

💯認識線程池

  1. 生活場景類比

假設你開了家餐廳:

  • 不用線程池:顧客點餐時現招服務員(創建線程),點完餐辭退(銷毀線程)。高峰期頻繁招人→效率低、資源浪費。
  • 用線程池:提前培訓3個固定服務員(核心線程),再備2個臨時工(最大線程擴展),顧客排隊(任務隊列)。任務來了直接分配,用完回池待命→效率翻倍!
  1. 技術核心價值

線程池通過 “線程復用、隊列緩沖、拒絕策略” 解決三大問題:

問題線程池如何解決?
線程創建銷毀開銷大提前創建線程,復用現有線程
線程數失控(OOM)限制最大線程數,任務排隊緩沖
任務突發無預案配置拒絕策略(任務爆倉時如何處理)

💯創建線程池

線程池的“靈魂類”是 ThreadPoolExecutor,像給“勞務公司”定規則:招多少固定工人、最多擴多少臨時工、任務咋排隊…

  1. 構造參數解析(類比餐廳管理)

創建線程池時,需設置7個核心參數,每個參數對應餐廳運營規則:

參數名作用(技術解釋)餐廳類比
corePoolSize核心線程數(一直保留的線程)固定員工數(3個長期服務員)
maximumPoolSize最大線程數(核心+臨時工總數)最多雇5人(3固定+2臨時)
keepAliveTime臨時工空閑超時時間臨時工沒事做→30秒后辭退
TimeUnit時間單位(秒/分等)時間標準(秒)
workQueue任務隊列(存放待處理任務)顧客排隊區(最多排3人)
threadFactory線程工廠(如何創建線程)招聘流程(統一培訓工人)
RejectedExecutionHandler拒絕策略(任務滿時如何處理新任務)排隊滿了→拒絕新顧客
  1. 任務拒絕策略:四種應急預案

當線程池忙到極限(核心線程+臨時工都在干活,隊列也排滿)時,新任務怎么處理?這就需要 拒絕策略——相當于“餐廳排隊滿了,如何應對新顧客”。

策略類說明(餐廳類比)代碼示例效果
AbortPolicy(默認)直接拒絕,拋 RejectedExecutionException 異常新顧客被拒,餐廳拋“無法接待”異常
DiscardPolicy默默丟棄任務,不拋異常新顧客被無視,餐廳繼續忙
DiscardOldestPolicy丟棄隊列中最久的任務,把新任務加入隊列趕走最早排隊的顧客,讓新顧客進隊
CallerRunsPolicy讓提交任務的線程(主線程)自己執行任務老板親自幫新顧客點餐(主線程執行)
  1. 代碼示例:拒絕策略實戰(處理Runnable任務)

模擬“餐廳忙到爆”場景:核心3線程、隊列3任務、最多5線程→當提交第9個任務時觸發拒絕策略。

import java.util.concurrent.*;public class RejectPolicyDemo {public static void main(String[] args) {// 測試四種拒絕策略(解開注釋切換)testRejectPolicy(new ThreadPoolExecutor.AbortPolicy());// testRejectPolicy(new ThreadPoolExecutor.DiscardPolicy());// testRejectPolicy(new ThreadPoolExecutor.DiscardOldestPolicy());// testRejectPolicy(new ThreadPoolExecutor.CallerRunsPolicy());}private static void testRejectPolicy(RejectedExecutionHandler policy) {System.out.println("===== 測試策略:" + policy.getClass().getSimpleName() + " =====");ThreadPoolExecutor pool = new ThreadPoolExecutor(3, // 核心3人5, // 最多5人30, TimeUnit.SECONDS,new ArrayBlockingQueue<>(3), // 隊列最多3個任務Executors.defaultThreadFactory(),policy // 設置拒絕策略);// 提交9個任務(3核心+3隊列+2臨時=8 → 第9個觸發拒絕)for (int i = 1; i <= 9; i++) {final int taskId = i;try {pool.execute(() -> { // 執行Runnable任務System.out.println(Thread.currentThread().getName() + " 處理任務:" + taskId);try { Thread.sleep(1000); } catch (InterruptedException e) {}});} catch (RejectedExecutionException e) {System.out.println("任務 " + taskId + " 被拒絕!策略:" + policy.getClass().getSimpleName());}}pool.shutdown();}
}
  1. 任務執行流程
    提交任務時,線程池按以下邏輯處理(對應餐廳流程):
  • 核心線程先干活:3個核心線程空閑→直接分配任務。
  • 核心忙→任務排隊:核心線程占滿→任務進隊列(最多3個)。
  • 隊列滿→招臨時工:隊列排滿→創建臨時線程(最多擴到5個)。
  • 全忙+隊列滿→觸發拒絕策略:臨時工也占滿→按配置的策略拒絕新任務。

💯處理Runnable任務

Runnable 是“無返回值任務”的代表,用 execute() 方法提交給線程池。

  1. 代碼示例:提交Runnable任務
// 定義一個Runnable任務(像“點餐任務”)
class OrderTask implements Runnable {private int taskId;public OrderTask(int taskId) { this.taskId = taskId; }@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + " 處理訂單:" + taskId);try { Thread.sleep(1000); } catch (InterruptedException e) {}}
}// 提交任務到線程池
public static void main(String[] args) {ThreadPoolExecutor pool = ...; // 同前創建的線程池(可配置拒絕策略)for (int i = 1; i <= 5; i++) {pool.execute(new OrderTask(i)); // 執行Runnable任務}pool.shutdown();
}
  1. 方法總結
方法作用適用任務類型
execute(Runnable)提交無返回值任務Runnable

💯處理Callable任務

Callable 是“有返回值任務”的代表,用 submit() 提交,通過 Future 獲取結果。

  1. 代碼示例:計算1~100的和(帶返回值)
import java.util.concurrent.*;public class CallableDemo {public static void main(String[] args) throws Exception {// 1. 創建線程池(復用之前的配置,可含拒絕策略)ThreadPoolExecutor pool = new ThreadPoolExecutor(3, 5, 30, TimeUnit.SECONDS,new ArrayBlockingQueue<>(3),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());// 2. 定義Callable任務(計算1~100的和)Callable<Integer> sumTask = () -> {int sum = 0;for (int i = 1; i <= 100; i++) sum += i;return sum; // 返回結果};// 3. 提交任務,獲取Future(結果的“憑證”)Future<Integer> future = pool.submit(sumTask);// 4. 獲取結果(任務未完成時,get()會阻塞等待)System.out.println("計算結果:" + future.get()); // 輸出5050// 5. 關閉線程池pool.shutdown();}
}
  1. 方法總結
方法作用適用任務類型
submit(Callable<T>)提交有返回值任務,返回 Future<T>Callable
Future.get()獲取任務結果(阻塞等待或超時等待)-

💯通過Executors創建線程池

Executors 是線程池“工具類”,像“快捷模板”,但不適合生產環境(阿里開發手冊強制禁用!)。

  1. 常用快捷方法
方法名特點(技術解釋)餐廳類比潛在風險
newFixedThreadPool(3)固定3個核心線程,隊列無界固定3個服務員,排隊不限任務堆積→內存溢出(OOM)
newSingleThreadExecutor()單線程,隊列無界只有1個服務員,排隊不限同上(隊列無界)
newCachedThreadPool()線程數彈性擴容(最多 Integer.MAX_VALUE無限招臨時工線程數爆炸→OOM
  1. 代碼示例:FixedThreadPool
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;public class ExecutorsDemo {public static void main(String[] args) {// 快捷創建:固定3個線程的線程池ExecutorService pool = Executors.newFixedThreadPool(3);// 提交任務(用法和 `ThreadPoolExecutor` 一致)for (int i = 1; i <= 5; i++) {final int taskId = i;pool.execute(() -> {System.out.println(Thread.currentThread().getName() + " 處理任務:" + taskId);try { Thread.sleep(1000); } catch (InterruptedException e) {}});}pool.shutdown();}
}
  1. 為什么“不推薦”?
    阿里巴巴Java開發手冊 強制要求:

線程池不允許用 Executors 創建,必須用 ThreadPoolExecutor

原因:

  • FixedThreadPool/SingleThreadExecutor:隊列是 Integer.MAX_VALUE(無限排隊),任務堆積會撐爆內存(OOM)。
  • CachedThreadPool:線程數上限是 Integer.MAX_VALUE(無限招人),線程太多也會OOM。

💯線程數配置公式

線程池的核心/最大線程數配置,必須根據任務類型調整,否則會嚴重影響效率。先理解兩種任務類型:

  1. 任務類型定義
類型特點(餐廳類比)示例任務
CPU密集型任務瘋狂“占用CPU”(如計算、加密),線程幾乎不空閑復雜數學運算、圖片壓縮
IO密集型任務大部分時間“等IO”(如讀寫文件、網絡請求),CPU空閑數據庫查詢、文件上傳、接口調用
  1. 配置公式(基于CPU核心數 N

線程池的核心思路:讓CPU盡可能不空閑,同時避免線程過多導致切換開銷

任務類型配置公式原理說明
CPU密集型核心線程數 = N + 1避免線程等待時CPU完全空閑,+1應對偶爾阻塞
IO密集型核心線程數 = N * 2(或 N * 5 等)利用IO等待的空閑時間,多線程并行處理任務
  1. 代碼示例:根據任務類型配置線程池
import java.util.concurrent.*;public class TaskTypeConfig {public static void main(String[] args) {int cpuCore = Runtime.getRuntime().availableProcessors(); // 獲取CPU核心數System.out.println("CPU核心數:" + cpuCore);// 1. CPU密集型任務:核心數 = cpuCore + 1ThreadPoolExecutor cpuPool = new ThreadPoolExecutor(cpuCore + 1, cpuCore + 1, // 最大線程數同核心(CPU沒空,無需臨時工)30, TimeUnit.SECONDS,new ArrayBlockingQueue<>(100));// 2. IO密集型任務:核心數 = cpuCore * 2ThreadPoolExecutor ioPool = new ThreadPoolExecutor(cpuCore * 2, cpuCore * 4, // 最大線程數適當擴展(應對突發IO任務)30, TimeUnit.SECONDS,new ArrayBlockingQueue<>(100));// 提交任務(按類型分配)cpuPool.execute(() -> heavyCalculation()); // CPU密集型ioPool.execute(() -> fetchData()); // IO密集型}// CPU密集型任務:瘋狂計算private static void heavyCalculation() {long result = 0;for (long i = 0; i < 1000000000L; i++) {result += i;}System.out.println("計算結果:" + result);}// IO密集型任務:模擬網絡請求private static void fetchData() {try {Thread.sleep(1000); // 模擬IO等待System.out.println("網絡數據獲取完成");} catch (InterruptedException e) {}}
}
  1. 小結:線程池配置實戰指南
場景任務類型核心線程數公式拒絕策略建議典型案例
計算密集(如加密)CPU密集型CPU核心數 + 1AbortPolicy(拋異常提醒)大數據排序、視頻編碼
網絡/IO 密集(如接口調用)IO密集型CPU核心數 * 2CallerRunsPolicy(降級執行)微服務調用、文件上傳

??并發與并行

在多線程的世界里,并發并行是經常被混淆的概念,但它們的本質區別直接影響程序的效率。

1.定義:宏觀與微觀的區別

簡單說,兩者的核心差異在于 “任務是否真正同時執行”,用表格對比更清晰:

概念技術解釋(CPU視角)生活化比喻
并發多個任務交替執行(單核CPU也能實現)餐廳1個服務員交替招呼3桌客人
并行多個任務同時執行(需要多核CPU支持)餐廳3個服務員同時招呼3桌客人
  1. 直觀示例:處理任務的兩種方式

假設你需要完成3件事:

  • 任務A:煮咖啡(需5分鐘,大部分時間等水開,CPU空閑)
  • 任務B:煎雞蛋(需3分鐘,全程占用CPU,不能停)
  • 任務C:烤面包(需4分鐘,全程占用CPU,不能停)

🌰 并發執行(單核CPU場景)

1個服務員處理3桌客人:

  1. 啟動任務A(放水煮咖啡)→ 水開前CPU空閑,切換到任務B煎雞蛋(3分鐘全程占用CPU)→
  2. 任務B完成后,切換到任務C烤面包(4分鐘全程占用CPU)→
  3. 最后回到任務A,咖啡煮好收尾。

特點

  • 宏觀上:3個任務“同時進行”(你感覺在同步推進);
  • 微觀上:CPU核心在任務間快速交替切換,實際同一時間只做一件事。

🌰 并行執行(多核CPU場景)

3個服務員同時開工:

  • 服務員1負責任務A(煮咖啡,等水開時CPU自動空閑)→
  • 服務員2負責任務B(煎雞蛋,全程占用1個CPU核心)→
  • 服務員3負責任務C(烤面包,全程占用另1個CPU核心)。

特點

  • 宏觀+微觀上:3個任務真正同時執行,效率翻倍(前提是CPU有多個核心)。
  1. Java多線程中的體現
  • 單核CPU:無論創建多少線程,線程池里的任務都只能并發執行(線程交替占用唯一的CPU核心)。
  • 多核CPU:線程池中的多個線程可以并行執行(不同核心同時跑任務),剩余線程繼續并發交替(充分利用多核+等待時間)。
  1. 小結

理解這兩個概念,才能設計出高效的多線程方案:

維度并發(Concurrency)并行(Parallelism)
執行本質交替執行(宏觀同時,微觀交替)同時執行(宏觀+微觀都同時)
CPU依賴單核即可實現必須多核支持
典型場景IO密集型任務(利用等待時間)CPU密集型任務(榨干多核性能)

實戰邏輯

  • 處理網絡請求、文件讀寫等IO密集型任務→ 用并發讓線程交替利用等待時間(線程池配多線程,哪怕單核也能高效);
  • 處理加密計算、大數據排序等CPU密集型任務→ 用并行讓多核同時開工(線程池核心數貼近CPU核心數,避免切換開銷)。

這就是線程池配置要區分任務類型的底層邏輯——并發與并行的協同,才是多線程的最大價值

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/88900.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/88900.shtml
英文地址,請注明出處:http://en.pswp.cn/web/88900.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

高斯代數基本定理的一種證明

代數基本定理 對于多項式 f(z)anznan?1zn?1?a1za0f(z) a_n z^n a_{n-1} z^{n-1} \cdots a_1 z a_0f(z)an?znan?1?zn?1?a1?za0?&#xff08;其中 n>1n > 1n>1 且 an,a0≠0a_n, a_0 \neq 0an?,a0?0&#xff09;&#xff0c;它在復數域內有根。 f(z)U…

【K8S】Kubernetes 使用 Ingress-Nginx 基于 Cookie 實現會話保持的負載均衡

文章目錄 1. 創建測試應用 Deployment3. 配置基于 Cookie 的 Ingress4. 部署與測試步驟(1) 應用配置(2) 獲取 Ingress IP(3) 測試會話保持(4) 使用 Nginx 取消域名的限制(僅推薦測試使用)5、生產優化建議6、獨立 Nginx 配置參考在現代微服務架構中,負載均衡是保證高可用的關…

2.查詢操作-demo

在連接數據庫的基礎上步驟&#xff1a;Query-查詢關閉查詢db.Next()逐行輸出&#xff0c;并指定到當前變量Scan-掃描rows, err : db.Query("SELECT id,server_ip FROM softswitch_server_info")package main//查詢語句-demo //關鍵字-queryimport ("database/sq…

用OpenCV標定相機內參應用示例(C++和Python)

下面是一個完整的 使用 OpenCV 進行相機內參標定&#xff08;Camera Calibration&#xff09; 的示例&#xff0c;包括 C 和 Python 兩個版本&#xff0c;基于棋盤格圖案標定。一、目標&#xff1a;相機標定 通過拍攝多張帶有棋盤格圖案的圖像&#xff0c;估計相機的內參&#…

(二)OpenCV——邊緣增強與檢測

邊緣增強與檢測是圖像處理中的核心技術&#xff0c;其核心目標是突出圖像中的不連續區域&#xff08;邊緣&#xff09;&#xff0c;為后續的圖像分析提供基礎。一、基本概念邊緣本質上是圖像中灰度/顏色發生突變的區域&#xff0c;對應著&#xff1a;物體邊界表面方向改變材質變…

018 進程控制 —— 進程等待

&#x1f984; 個人主頁: 小米里的大麥-CSDN博客 &#x1f38f; 所屬專欄: Linux_小米里的大麥的博客-CSDN博客 &#x1f381; GitHub主頁: 小米里的大麥的 GitHub ?? 操作環境: Visual Studio 2022 文章目錄進程控制 —— 進程等待1. 進程等待必要性2. 常用等待方法&#xf…

PHP password_hash() 函數

password_hash() 函數用于創建密碼的散列&#xff08;hash&#xff09;PHP 版本要求: PHP 5 > 5.5.0, PHP 7語法string password_hash ( string $password , int $algo [, array $options ] )password_hash() 使用足夠強度的單向散列算法創建密碼的散列&#xff08;hash&…

理解Linux文件系統:從物理存儲到統一接口

目錄 一、狹義理解&#xff08;物理層面&#xff09; 二、廣義理解&#xff08;Linux系統視角&#xff09; 三、文件結構解析 四、系統實現機制 一、狹義理解&#xff08;物理層面&#xff09; 存儲特性&#xff1a;文件以二進制形式存儲在磁盤等永久性存儲介質中 介質特點…

前端接入海康威視攝像頭的三種方案

方案選擇?方案適用場景優缺點?Web SDK&#xff08;3.0&#xff09;??需要完整功能&#xff08;PTZ控制、錄像回放&#xff09;功能全&#xff0c;但需加載海康JS文件?RTSP轉Web播放?低延遲實時監控需后端轉碼&#xff08;如FFmpeg轉HLS&#xff09;?HTTP API?簡單截圖或…

openGL學習(Shader)

認識Shader在計算機圖形學中&#xff0c;Shader&#xff08;著色器&#xff09;是一種運行在 GPU&#xff08;圖形處理單元&#xff09;上的程序&#xff0c;用于控制圖形渲染過程中頂點和像素的處理。著色器是 OpenGL、Direct3D、Vulkan 等圖形 API 的核心組成部分&#xff0c…

webpack高級配置

一、了解webpack高級配置&#xff1a; 1、什么是webpack高級配置&#xff1a; 進行 Webpack 優化&#xff0c;讓代碼在編譯或者運行時性能更好 2、webpack優化從哪些方面入手&#xff1a; ① 提升開發體驗&#xff0c;增強開發和生產環境的代碼調試&#xff1a; 如果代碼編寫…

LLM表征工程還有哪些值得做的地方

LLM表征工程還有哪些值得做的地方 在大型語言模型(LLM)的表征工程領域,近年來涌現出多個具有突破性的創新方向,這些方法通過動態調整、多模態融合、結構化記憶增強等技術,顯著提升了模型的適應性、可解釋性和效率。 一、動態自適應表征:從靜態到動態的范式革新 傳統LL…

LabVIEW智能避障小車

?LabVIEW結合 NI、德州儀器&#xff08;TI&#xff09;、歐姆龍&#xff08;Omron&#xff09;等硬件&#xff0c;設計實現了一款具備智能避障、循跡功能的輪式機器人。系統支持手動操控與自主運行兩種模式&#xff0c;通過無線通信實時傳輸傳感器數據與圖像信息&#xff0c;在…

邏輯代數中的基本規則,代入規則和反演規則,對偶規則

本文探討了代入規則在邏輯等式中的應用&#xff0c;解釋了如何通過替換變量來保持等式的正確性&#xff0c;同時介紹了反演規則和對偶規則的概念。代入規則定義:在任何一個包含變量A的邏輯等式中&#xff0c;如果用另一個邏輯式代入式中的所有A位置&#xff0c;則等式依然成立反…

Javaweb使用websocket,請先連上demo好吧!很簡單的!

Javaweb使用websocket先看結構及效果MyWebSocketHandler用于處理消息WebSocketConfig用于配置建聯地址等SchedulerConfig必須配置這個MyWebSocketInterceptor建聯的攔截器SpringBootWebsocketApplication啟動類POM依賴展示效果源碼先看結構及效果 MyWebSocketHandler用于處理消…

文心大模型4.5開源測評:保姆級部署教程+多維度測試驗證

前言&#xff1a;國產大模型開源的破局時刻 2025年6月百度文心大模型4.5系列的開源&#xff0c;標志著國產AI從"技術跟跑"向"生態共建"的關鍵跨越。 文心大模型4.5是百度自主研發的新一代原生多模態基礎大模型&#xff0c;通過多個模態聯合建模實現協同優…

前端學習5:Float學習(僅簡單了解,引出flex)

一、Float基礎概念1. 設計初衷&#xff1a; float最初是為實現文字環繞圖片的效果&#xff08;類似雜志排版&#xff09;&#xff0c;后來被開發者用來做頁面布局。2. 核心特性&#xff1a;使元素脫離普通文檔流&#xff08;但仍在DOM中&#xff09;元素會向左/右浮動&#xff…

08-自然壁紙實戰教程-視頻列表-云

08-自然壁紙實戰教程-視頻列表 前言 視頻列表頁面本質上也是一個數據展示的列表&#xff0c;不同之處在于之前是是展示壁紙&#xff0c;Image組件負責渲染&#xff0c;這里展示的是視頻&#xff0c;使用Video組件&#xff0c;另外視頻頁面也實現了下載的基本功能&#xff0c;…

SCI特刊征稿

我們團隊聯合北京工業大學研究團隊在SCI源刊CMC組織了特刊SI: Advanced Edge Computing and Artificial Intelligence in Smart Environment,主要收錄邊緣計算和人工智能方向的文章&#xff0c;歡迎領域專家和學者投稿&#xff0c;網址https://www.techscience.com/cmc/special…

DO,VO,DTO.....

在 Java 項目里&#xff08;尤其是 Spring、MyBatis 這類框架&#xff09;&#xff0c;經常會看到一堆以 O 結尾的類&#xff1a;VO、DO、DTO、BO、POJO……它們本質上都是普通的 Java Bean&#xff08;即 POJO&#xff09;&#xff0c;但職責和出現的位置不同。下面用“用戶下…