Java多線程編程怎么學(xué)?線程同步與線程池實戰(zhàn)詳解
1.1 本章學(xué)習(xí)目標與重點
?? 掌握線程的核心概念,理解進程與線程的區(qū)別和聯(lián)系。
?? 熟練掌握線程的三種創(chuàng)建方式,理解線程的生命周期及狀態(tài)轉(zhuǎn)換。
?? 掌握線程同步與鎖機制,解決多線程并發(fā)安全問題。
?? 了解線程池的核心原理與使用方法,提升多線程程序性能。
?? 本章重點是 線程同步機制 和 線程池的實戰(zhàn)應(yīng)用,這是多線程開發(fā)中的核心難點和高頻考點。
1.2 多線程核心概念
1.2.1 進程與線程的區(qū)別
?? 進程是操作系統(tǒng)進行資源分配和調(diào)度的基本單位,每個進程都有獨立的內(nèi)存空間和系統(tǒng)資源。比如打開一個 Java 程序,就會啟動一個進程。
?? 線程是進程的執(zhí)行單元,是 CPU 調(diào)度和執(zhí)行的基本單位。一個進程可以包含多個線程,這些線程共享進程的內(nèi)存空間和資源。
| 對比維度 | 進程 | 線程 |
|---|---|---|
| 資源分配 | 擁有獨立的內(nèi)存空間和資源 | 共享所屬進程的內(nèi)存和資源 |
| 開銷成本 | 創(chuàng)建和銷毀開銷大 | 創(chuàng)建和銷毀開銷小 |
| 調(diào)度方式 | 由操作系統(tǒng)內(nèi)核調(diào)度 | 由進程內(nèi)部調(diào)度 |
| 獨立性 | 進程之間相互獨立 | 線程之間共享資源,依賴性強 |
? 核心結(jié)論:線程是輕量級的進程,多線程編程可以充分利用 CPU 資源,提升程序的并發(fā)執(zhí)行效率。
1.2.2 為什么需要多線程
在單線程程序中,代碼是串行執(zhí)行的,當遇到耗時操作(如文件讀寫、網(wǎng)絡(luò)請求)時,程序會阻塞等待,造成 CPU 資源浪費。
多線程的優(yōu)勢體現(xiàn)在以下場景:
- 后臺任務(wù)處理:如電商系統(tǒng)的訂單超時取消、數(shù)據(jù)同步等。
- 異步操作:如用戶注冊后發(fā)送短信驗證碼,不阻塞主流程。
- 提高 CPU 利用率:如計算密集型任務(wù),多線程可以充分利用多核 CPU。
1.3 線程的創(chuàng)建方式
1.3.1 方式1:繼承 Thread 類
實現(xiàn)步驟
① ?? 定義一個類繼承 Thread 類。
② ?? 重寫 run() 方法,編寫線程執(zhí)行的業(yè)務(wù)邏輯。
③ ?? 創(chuàng)建線程對象,調(diào)用 start() 方法啟動線程。
代碼實操
/**
* 繼承Thread類創(chuàng)建線程
*/
public class MyThread extends Thread {
// 線程名稱
private String threadName;
public MyThread(String threadName) {
this.threadName = threadName;
}
// 重寫run方法,定義線程執(zhí)行邏輯
@Override
public void run() {
for (int i = 1; i <= 5; i++) {
System.out.println(threadName + " 執(zhí)行:" + i);
// 模擬線程執(zhí)行耗時操作
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
// 創(chuàng)建線程對象
MyThread thread1 = new MyThread("線程A");
MyThread thread2 = new MyThread("線程B");
// 啟動線程:注意是調(diào)用start()方法,不是run()方法
thread1.start();
thread2.start();
}
}
輸出結(jié)果(順序不固定,體現(xiàn)線程并發(fā)執(zhí)行)
線程A 執(zhí)行:1 線程B 執(zhí)行:1 線程A 執(zhí)行:2 線程B 執(zhí)行:2 線程A 執(zhí)行:3 線程B 執(zhí)行:3 線程A 執(zhí)行:4 線程B 執(zhí)行:4 線程A 執(zhí)行:5 線程B 執(zhí)行:5
?? 注意事項:啟動線程必須調(diào)用 start() 方法,直接調(diào)用 run() 方法會變成普通方法的串行執(zhí)行。
1.3.2 方式2:實現(xiàn) Runnable 接口
實現(xiàn)步驟
① ?? 定義一個類實現(xiàn) Runnable 接口。
② ?? 重寫 run() 方法,編寫線程執(zhí)行邏輯。
③ ?? 創(chuàng)建 Runnable 實現(xiàn)類對象,作為 Thread 構(gòu)造方法的參數(shù)。
④ ?? 調(diào)用 start() 方法啟動線程。
代碼實操
/**
* 實現(xiàn)Runnable接口創(chuàng)建線程
*/
public class MyRunnable implements Runnable {
private String taskName;
public MyRunnable(String taskName) {
this.taskName = taskName;
}
@Override
public void run() {
for (int i = 1; i <= 5; i++) {
System.out.println(taskName + " 執(zhí)行:" + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
// 創(chuàng)建任務(wù)對象
MyRunnable task1 = new MyRunnable("任務(wù)A");
MyRunnable task2 = new MyRunnable("任務(wù)B");
// 創(chuàng)建線程對象,傳入任務(wù)
Thread thread1 = new Thread(task1);
Thread thread2 = new Thread(task2);
// 啟動線程
thread1.start();
thread2.start();
}
}
方式2的優(yōu)勢
- 避免單繼承的局限性:Java 是單繼承機制,繼承
Thread類后無法再繼承其他類,而實現(xiàn)Runnable接口可以。 - 資源共享:多個線程可以共享同一個
Runnable任務(wù)對象,適合多線程處理同一份資源的場景。
資源共享案例:多線程售票
/**
* 多線程共享資源:模擬售票系統(tǒng)
*/
public class TicketRunnable implements Runnable {
// 共享資源:10張票
private int ticketNum = 10;
@Override
public void run() {
while (ticketNum > 0) {
// 模擬售票耗時
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 售出第 " + ticketNum-- + " 張票");
}
}
public static void main(String[] args) {
// 一個任務(wù)對象,多個線程共享
TicketRunnable ticketTask = new TicketRunnable();
// 三個線程模擬三個售票窗口
Thread window1 = new Thread(ticketTask, "窗口1");
Thread window2 = new Thread(ticketTask, "窗口2");
Thread window3 = new Thread(ticketTask, "窗口3");
window1.start();
window2.start();
window3.start();
}
}
輸出結(jié)果(存在線程安全問題,后面會解決)
窗口1 售出第 10 張票 窗口2 售出第 9 張票 窗口3 售出第 8 張票 窗口1 售出第 7 張票 窗口2 售出第 6 張票 窗口3 售出第 5 張票 窗口1 售出第 4 張票 窗口2 售出第 3 張票 窗口3 售出第 2 張票 窗口1 售出第 1 張票
1.3.3 方式3:實現(xiàn) Callable 接口(帶返回值)
實現(xiàn)步驟
① ?? 定義一個類實現(xiàn) Callable<V> 接口,V 是返回值類型。
② ?? 重寫 call() 方法,編寫線程執(zhí)行邏輯,該方法可以拋出異常。
③ ?? 創(chuàng)建 Callable 實現(xiàn)類對象,通過 FutureTask 包裝。
④ ?? 將 FutureTask 作為 Thread 構(gòu)造方法參數(shù),啟動線程。
⑤ ?? 調(diào)用 FutureTask 的 get() 方法獲取返回值。
代碼實操
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
/**
* 實現(xiàn)Callable接口創(chuàng)建線程(帶返回值)
*/
public class MyCallable implements Callable<Integer> {
// 計算1到n的累加和
private int n;
public MyCallable(int n) {
this.n = n;
}
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 1; i <= n; i++) {
sum += i;
System.out.println("計算中:" + i + ",當前和為:" + sum);
Thread.sleep(200);
}
return sum;
}
public static void main(String[] args) throws Exception {
// 創(chuàng)建Callable任務(wù)
MyCallable callableTask = new MyCallable(10);
// 用FutureTask包裝,用于獲取返回值
FutureTask<Integer> futureTask = new FutureTask<>(callableTask);
// 創(chuàng)建線程并啟動
Thread thread = new Thread(futureTask);
thread.start();
// 獲取返回值:get()方法會阻塞,直到線程執(zhí)行完成
Integer result = futureTask.get();
System.out.println("1到10的累加和為:" + result);
}
}
輸出結(jié)果
計算中:1,當前和為:1 計算中:2,當前和為:3 計算中:3,當前和為:6 計算中:4,當前和為:10 計算中:5,當前和為:15 計算中:6,當前和為:21 計算中:7,當前和為:28 計算中:8,當前和為:36 計算中:9,當前和為:45 計算中:10,當前和為:55 1到10的累加和為:55
? 核心結(jié)論:三種創(chuàng)建方式對比
| 創(chuàng)建方式 | 優(yōu)點 | 缺點 |
|---|---|---|
| 繼承 Thread | 編程簡單,直接使用 this 獲取線程 | 單繼承局限,無法共享資源 |
| 實現(xiàn) Runnable | 避免單繼承,支持資源共享 | 無返回值,無法拋出受檢異常 |
| 實現(xiàn) Callable | 有返回值,支持異常拋出 | 編程相對復(fù)雜 |
1.4 線程的生命周期與狀態(tài)轉(zhuǎn)換
1.4.1 線程的六種狀態(tài)
Java 中線程的生命周期包含六種狀態(tài),定義在 Thread.State 枚舉中:
- NEW(新建狀態(tài)):線程對象已創(chuàng)建,但未調(diào)用
start()方法。 - RUNNABLE(可運行狀態(tài)):調(diào)用
start()方法后,線程處于就緒或運行中。- 就緒狀態(tài):線程等待 CPU 調(diào)度。
- 運行狀態(tài):線程獲取 CPU 資源,執(zhí)行
run()方法。
- BLOCKED(阻塞狀態(tài)):線程等待獲取鎖資源時進入該狀態(tài)。
- WAITING(等待狀態(tài)):線程調(diào)用
wait()、join()等方法后進入,需要被其他線程喚醒。 - TIMED_WAITING(超時等待狀態(tài)):線程調(diào)用
sleep(long)、wait(long)等方法后進入,超時自動喚醒。 - TERMINATED(終止狀態(tài)):線程執(zhí)行完畢或異常終止。
1.4.2 線程狀態(tài)轉(zhuǎn)換圖(核心流程)
NEW → RUNNABLE:調(diào)用 start() 方法 RUNNABLE → BLOCKED:競爭鎖失敗 BLOCKED → RUNNABLE:獲取到鎖資源 RUNNABLE → WAITING:調(diào)用 wait()/join() 方法 WAITING → RUNNABLE:其他線程調(diào)用 notify()/notifyAll() 方法 RUNNABLE → TIMED_WAITING:調(diào)用 sleep(long)/wait(long) 方法 TIMED_WAITING → RUNNABLE:超時自動喚醒或被其他線程喚醒 RUNNABLE → TERMINATED:run() 方法執(zhí)行完畢或異常終止
1.4.3 常用線程控制方法
| 方法 | 作用 | 注意事項 |
|---|---|---|
start() | 啟動線程 | 一個線程只能調(diào)用一次 |
sleep(long millis) | 讓線程休眠指定時間 | 不會釋放持有的鎖資源 |
wait() | 讓線程進入等待狀態(tài) | 必須在同步代碼塊中調(diào)用,會釋放鎖 |
notify() | 喚醒一個等待的線程 | 必須在同步代碼塊中調(diào)用 |
notifyAll() | 喚醒所有等待的線程 | 必須在同步代碼塊中調(diào)用 |
join() | 等待該線程執(zhí)行完畢 | 可以實現(xiàn)線程的順序執(zhí)行 |
代碼實操:線程的順序執(zhí)行(join 方法)
/**
* 使用join方法實現(xiàn)線程順序執(zhí)行
*/
public class ThreadJoinDemo {
public static void main(String[] args) throws InterruptedException {
Thread threadA = new Thread(() -> {
for (int i = 1; i <= 3; i++) {
System.out.println("線程A執(zhí)行:" + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread threadB = new Thread(() -> {
for (int i = 1; i <= 3; i++) {
System.out.println("線程B執(zhí)行:" + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
// 啟動線程A
threadA.start();
// 等待線程A執(zhí)行完畢,再啟動線程B
threadA.join();
threadB.start();
}
}
輸出結(jié)果(線程A執(zhí)行完才會執(zhí)行線程B)
線程A執(zhí)行:1 線程A執(zhí)行:2 線程A執(zhí)行:3 線程B執(zhí)行:1 線程B執(zhí)行:2 線程B執(zhí)行:3
1.5 線程同步與并發(fā)安全
1.5.1 并發(fā)安全問題的產(chǎn)生
當多個線程同時操作共享資源時,會導(dǎo)致數(shù)據(jù)不一致的問題。比如前面的售票案例,在極端情況下會出現(xiàn)超賣或重復(fù)售票的問題。
問題復(fù)現(xiàn):多線程售票的并發(fā)安全問題
public class UnsafeTicketDemo implements Runnable {
private int ticketNum = 10;
@Override
public void run() {
while (ticketNum > 0) {
// 模擬網(wǎng)絡(luò)延遲,放大并發(fā)問題
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 核心問題:判斷和操作不是原子性的
System.out.println(Thread.currentThread().getName() + " 售出第 " + ticketNum-- + " 張票");
}
}
public static void main(String[] args) {
UnsafeTicketDemo task = new UnsafeTicketDemo();
new Thread(task, "窗口1").start();
new Thread(task, "窗口2").start();
new Thread(task, "窗口3").start();
}
}
可能出現(xiàn)的錯誤結(jié)果
窗口1 售出第 10 張票 窗口2 售出第 10 張票 窗口3 售出第 9 張票 窗口1 售出第 8 張票 窗口2 售出第 7 張票 窗口3 售出第 6 張票 窗口1 售出第 5 張票 窗口2 售出第 4 張票 窗口3 售出第 3 張票 窗口1 售出第 2 張票 窗口2 售出第 1 張票 窗口3 售出第 0 張票
可以看到出現(xiàn)了重復(fù)售票和售出0號票的問題,這就是典型的并發(fā)安全問題。
1.5.2 解決方案1:同步代碼塊(synchronized)
?? 同步代碼塊的核心是鎖機制,通過 synchronized 關(guān)鍵字鎖定一個對象,保證同一時間只有一個線程能執(zhí)行代碼塊中的內(nèi)容。
語法格式
synchronized (鎖對象) {
// 需要同步的代碼(操作共享資源的代碼)
}
代碼實操:同步代碼塊解決售票問題
public class SafeTicketDemo1 implements Runnable {
private int ticketNum = 10;
// 定義鎖對象:必須是多個線程共享的對象
private final Object lock = new Object();
@Override
public void run() {
while (true) {
synchronized (lock) {
if (ticketNum > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 售出第 " + ticketNum-- + " 張票");
} else {
break;
}
}
}
}
public static void main(String[] args) {
SafeTicketDemo1 task = new SafeTicketDemo1();
new Thread(task, "窗口1").start();
new Thread(task, "窗口2").start();
new Thread(task, "窗口3").start();
}
}
輸出結(jié)果(無重復(fù)、無超賣,解決并發(fā)安全問題)
窗口1 售出第 10 張票 窗口1 售出第 9 張票 窗口2 售出第 8 張票 窗口2 售出第 7 張票 窗口3 售出第 6 張票 窗口3 售出第 5 張票 窗口1 售出第 4 張票 窗口1 售出第 3 張票 窗口2 售出第 2 張票 窗口2 售出第 1 張票
?? 注意事項:鎖對象必須是多個線程共享的對象,否則無法實現(xiàn)同步效果。
1.5.3 解決方案2:同步方法(synchronized)
?? 同步方法是將 synchronized 關(guān)鍵字修飾在方法上,等價于鎖定當前對象(this)。
語法格式
public synchronized 返回值類型 方法名(參數(shù)列表) {
// 需要同步的代碼
}
代碼實操:同步方法解決售票問題
public class SafeTicketDemo2 implements Runnable {
private int ticketNum = 10;
@Override
public void run() {
while (true) {
if (!sellTicket()) {
break;
}
}
}
// 同步方法:鎖定的是this對象
private synchronized boolean sellTicket() {
if (ticketNum > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 售出第 " + ticketNum-- + " 張票");
return true;
} else {
return false;
}
}
public static void main(String[] args) {
SafeTicketDemo2 task = new SafeTicketDemo2();
new Thread(task, "窗口1").start();
new Thread(task, "窗口2").start();
new Thread(task, "窗口3").start();
}
}
1.5.4 解決方案3:Lock 鎖(JUC 包)
?? java.util.concurrent.locks.Lock 是 JDK 5 新增的鎖機制,相比 synchronized 更加靈活,可以手動控制鎖的獲取和釋放。
常用實現(xiàn)類是 ReentrantLock(可重入鎖)。
實現(xiàn)步驟
① ?? 創(chuàng)建 ReentrantLock 對象。
② ?? 在需要同步的代碼前調(diào)用 lock() 方法獲取鎖。
③ ?? 在 finally 塊中調(diào)用 unlock() 方法釋放鎖,確保鎖一定會被釋放。
代碼實操:Lock 鎖解決售票問題
import java.util.concurrent.locks.ReentrantLock;
public class SafeTicketDemo3 implements Runnable {
private int ticketNum = 10;
// 創(chuàng)建可重入鎖對象
private final ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
// 獲取鎖
lock.lock();
try {
if (ticketNum > 0) {
Thread.sleep(100);
System.out.println(Thread.currentThread().getName() + " 售出第 " + ticketNum-- + " 張票");
} else {
break;
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 釋放鎖:必須放在finally塊中
lock.unlock();
}
}
}
public static void main(String[] args) {
SafeTicketDemo3 task = new SafeTicketDemo3();
new Thread(task, "窗口1").start();
new Thread(task, "窗口2").start();
new Thread(task, "窗口3").start();
}
}
? 核心結(jié)論:synchronized 和 Lock 的對比
| 特性 | synchronized | Lock |
|---|---|---|
| 鎖獲取釋放 | 自動獲取和釋放 | 手動調(diào)用 lock()/unlock() |
| 靈活性 | 低 | 高(支持公平鎖/非公平鎖) |
| 可中斷性 | 不支持 | 支持 |
| 條件變量 | 不支持 | 支持(Condition) |
1.6 線程池:高效管理多線程
1.6.1 為什么需要線程池
頻繁創(chuàng)建和銷毀線程會消耗大量系統(tǒng)資源,線程池可以復(fù)用線程,降低系統(tǒng)開銷。線程池的核心優(yōu)勢:
- 降低資源消耗:復(fù)用已創(chuàng)建的線程,減少創(chuàng)建和銷毀線程的開銷。
- 提高響應(yīng)速度:任務(wù)到達時,無需等待線程創(chuàng)建即可立即執(zhí)行。
- 便于線程管理:可以控制最大并發(fā)數(shù),避免線程過多導(dǎo)致資源耗盡。
1.6.2 線程池的核心參數(shù)(ThreadPoolExecutor)
Java 中線程池的核心實現(xiàn)類是 ThreadPoolExecutor,其構(gòu)造方法包含 7 個核心參數(shù):
public ThreadPoolExecutor(
int corePoolSize, // 核心線程數(shù)
int maximumPoolSize, // 最大線程數(shù)
long keepAliveTime, // 非核心線程空閑存活時間
TimeUnit unit, // 時間單位
BlockingQueue<Runnable> workQueue, // 任務(wù)隊列
ThreadFactory threadFactory, // 線程工廠
RejectedExecutionHandler handler // 拒絕策略
)
參數(shù)說明
- 核心線程數(shù):線程池長期保持的線程數(shù)量,即使空閑也不會銷毀。
- 最大線程數(shù):線程池允許創(chuàng)建的最大線程數(shù)。
- 空閑存活時間:非核心線程空閑超過該時間會被銷毀。
- 任務(wù)隊列:存放等待執(zhí)行的任務(wù),當核心線程都在忙時,任務(wù)會進入隊列。
- 拒絕策略:當任務(wù)隊列滿且線程數(shù)達到最大時,如何處理新任務(wù)。
1.6.3 線程池的使用方式
方式1:通過 Executors 工具類創(chuàng)建(快速使用)
Executors 提供了幾種常用的線程池創(chuàng)建方法:
newFixedThreadPool(int nThreads):固定大小的線程池。newCachedThreadPool():緩存線程池,線程數(shù)可動態(tài)調(diào)整。newSingleThreadExecutor():單線程池,保證任務(wù)順序執(zhí)行。
代碼實操:固定大小線程池
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 固定大小線程池的使用
*/
public class ThreadPoolDemo {
public static void main(String[] args) {
// 1. 創(chuàng)建固定大小的線程池,核心線程數(shù)=最大線程數(shù)=3
ExecutorService executorService = Executors.newFixedThreadPool(3);
// 2. 提交10個任務(wù)
for (int i = 1; i <= 10; i++) {
int taskId = i;
executorService.submit(() -> {
System.out.println("任務(wù)" + taskId + " 由線程 " + Thread.currentThread().getName() + " 執(zhí)行");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
// 3. 關(guān)閉線程池
executorService.shutdown();
}
}
輸出結(jié)果(3個線程復(fù)用執(zhí)行10個任務(wù))
任務(wù)1 由線程 pool-1-thread-1 執(zhí)行 任務(wù)2 由線程 pool-1-thread-2 執(zhí)行 任務(wù)3 由線程 pool-1-thread-3 執(zhí)行 任務(wù)4 由線程 pool-1-thread-1 執(zhí)行 任務(wù)5 由線程 pool-1-thread-2 執(zhí)行 任務(wù)6 由線程 pool-1-thread-3 執(zhí)行 任務(wù)7 由線程 pool-1-thread-1 執(zhí)行 任務(wù)8 由線程 pool-1-thread-2 執(zhí)行 任務(wù)9 由線程 pool-1-thread-3 執(zhí)行 任務(wù)10 由線程 pool-1-thread-1 執(zhí)行
方式2:手動創(chuàng)建 ThreadPoolExecutor(推薦,更可控)
import java.util.concurrent.*;
/**
* 手動創(chuàng)建線程池(推薦方式)
*/
public class CustomThreadPoolDemo {
public static void main(String[] args) {
// 1. 定義線程池參數(shù)
int corePoolSize = 2;
int maximumPoolSize = 5;
long keepAliveTime = 60;
TimeUnit unit = TimeUnit.SECONDS;
// 任務(wù)隊列:容量為3的阻塞隊列
BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(3);
// 線程工廠:默認線程工廠
ThreadFactory threadFactory = Executors.defaultThreadFactory();
// 拒絕策略:丟棄任務(wù)并拋出異常
RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy();
// 2. 創(chuàng)建線程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(
corePoolSize,
maximumPoolSize,
keepAliveTime,
unit,
workQueue,
threadFactory,
handler
);
// 3. 提交任務(wù)
for (int i = 1; i <= 10; i++) {
int taskId = i;
try {
executor.submit(() -> {
System.out.println("任務(wù)" + taskId + " 由線程 " + Thread.currentThread().getName() + " 執(zhí)行");
Thread.sleep(1000);
});
} catch (Exception e) {
System.out.println("任務(wù)" + taskId + " 被拒絕:" + e.getMessage());
}
}
// 4. 關(guān)閉線程池
executor.shutdown();
}
}
輸出結(jié)果(當任務(wù)數(shù)超過 最大線程數(shù)+隊列容量=8 時,新任務(wù)被拒絕)
任務(wù)1 由線程 pool-1-thread-1 執(zhí)行 任務(wù)2 由線程 pool-1-thread-2 執(zhí)行 任務(wù)3 由線程 pool-1-thread-3 執(zhí)行 任務(wù)4 由線程 pool-1-thread-4 執(zhí)行 任務(wù)5 由線程 pool-1-thread-5 執(zhí)行 任務(wù)6 由線程 pool-1-thread-1 執(zhí)行 任務(wù)7 由線程 pool-1-thread-2 執(zhí)行 任務(wù)8 由線程 pool-1-thread-3 執(zhí)行 任務(wù)9 被拒絕:Task java.util.concurrent.FutureTask@6d311334 rejected from java.util.concurrent.ThreadPoolExecutor@682a0b20[Running, pool size = 5, active threads = 5, queued tasks = 3, completed tasks = 0] 任務(wù)10 被拒絕:Task java.util.concurrent.FutureTask@3d075dc0 rejected from java.util.concurrent.ThreadPoolExecutor@682a0b20[Running, pool size = 5, active threads = 5, queued tasks = 3, completed tasks = 0]
?? 注意事項:實際開發(fā)中推薦手動創(chuàng)建線程池,避免使用 Executors 工具類的默認方法,防止資源耗盡。
1.7 實戰(zhàn)案例:多線程實現(xiàn)文件批量下載
1.7.1 需求分析
?? 實現(xiàn)一個多線程文件下載工具,支持從指定 URL 列表批量下載文件,要求:
- 使用線程池管理下載線程,控制并發(fā)數(shù)。
- 記錄每個文件的下載狀態(tài)(成功/失?。?。
- 支持斷點續(xù)傳(可選)。
1.7.2 代碼實現(xiàn)
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
/**
* 多線程文件批量下載工具
*/
public class MultiThreadFileDownloader {
// 線程池:核心線程數(shù)2,最大線程數(shù)4
private static final ExecutorService EXECUTOR = new ThreadPoolExecutor(
2,
4,
60,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(2),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy()
);
// 下載狀態(tài)列表
private static final List<String> DOWNLOAD_STATUS = new ArrayList<>();
/**
* 下載單個文件
* @param fileUrl 文件URL
* @param savePath 保存路徑
*/
private static void downloadFile(String fileUrl, String savePath) {
HttpURLConnection connection = null;
InputStream in = null;
OutputStream out = null;
try {
URL url = new URL(fileUrl);
connection = (HttpURLConnection) url.openConnection();
connection.setConnectTimeout(5000);
connection.setReadTimeout(5000);
// 獲取文件名
String fileName = fileUrl.substring(fileUrl.lastIndexOf("/") + 1);
File saveFile = new File(savePath + File.separator + fileName);
// 讀取文件流并寫入本地
in = connection.getInputStream();
out = new FileOutputStream(saveFile);
byte[] buffer = new byte[1024 * 8];
int len;
while ((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
String successMsg = "成功:" + fileName;
synchronized (DOWNLOAD_STATUS) {
DOWNLOAD_STATUS.add(successMsg);
}
System.out.println(successMsg);
} catch (Exception e) {
String fileName = fileUrl.substring(fileUrl.lastIndexOf("/") + 1);
String failMsg = "失?。? + fileName + ",原因:" + e.getMessage();
synchronized (DOWNLOAD_STATUS) {
DOWNLOAD_STATUS.add(failMsg);
}
System.out.println(failMsg);
} finally {
// 關(guān)閉資源
try {
if (out != null) out.close();
if (in != null) in.close();
if (connection != null) connection.disconnect();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 批量下載文件
* @param urlList URL列表
* @param savePath 保存路徑
*/
public static void batchDownload(List<String> urlList, String savePath) {
// 檢查保存路徑是否存在
File saveDir = new File(savePath);
if (!saveDir.exists()) {
saveDir.mkdirs();
}
// 提交下載任務(wù)
for (String url : urlList) {
EXECUTOR.submit(() -> downloadFile(url, savePath));
}
// 關(guān)閉線程池
EXECUTOR.shutdown();
try {
// 等待所有任務(wù)完成
EXECUTOR.awaitTermination(1, TimeUnit.HOURS);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 打印下載結(jié)果
System.out.println("\n===== 下載完成 ======");
for (String status : DOWNLOAD_STATUS) {
System.out.println(status);
}
}
public static void main(String[] args) {
// 測試URL列表(替換為實際可下載的URL)
List<String> urlList = new ArrayList<>();
urlList.add("https://example.com/file1.txt");
urlList.add("https://example.com/file2.jpg");
urlList.add("https://example.com/file3.pdf");
urlList.add("https://example.com/file4.zip");
urlList.add("https://example.com/file5.doc");
// 批量下載到D盤download目錄
batchDownload(urlList, "D:\\download");
}
}
1.7.3 案例總結(jié)
? 這個多線程下載工具綜合運用了線程池、同步機制、IO流等知識,核心亮點:
- 使用線程池控制并發(fā)數(shù),避免線程過多導(dǎo)致系統(tǒng)資源耗盡。
- 使用
synchronized保證下載狀態(tài)列表的線程安全。 - 采用
CallerRunsPolicy拒絕策略,當任務(wù)過多時,由調(diào)用線程執(zhí)行,避免任務(wù)丟失。 - 完善的資源關(guān)閉和異常處理,保證程序健壯性。
1.8 本章總結(jié)
- 線程是進程的執(zhí)行單元,多線程可以提升程序并發(fā)效率,充分利用 CPU 資源。
- 線程有三種創(chuàng)建方式:繼承 Thread、實現(xiàn) Runnable、實現(xiàn) Callable,推薦使用后兩種。
- 線程的生命周期包含六種狀態(tài),核心是掌握狀態(tài)之間的轉(zhuǎn)換條件。
- 并發(fā)安全問題的根源是多個線程操作共享資源,解決方案有 synchronized 同步代碼塊/方法、Lock 鎖。
- 線程池可以復(fù)用線程,降低系統(tǒng)開銷,實際開發(fā)中推薦手動創(chuàng)建 ThreadPoolExecutor。
- 多線程編程的核心是線程同步和資源管理,要注意避免死鎖、內(nèi)存可見性等問題。
到此這篇關(guān)于Java多線程編程怎么學(xué)?線程同步與線程池實戰(zhàn)詳解的文章就介紹到這了,更多相關(guān)JAVA多線程編程實戰(zhàn)教程內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java for循環(huán)性能優(yōu)化實現(xiàn)解析
這篇文章主要介紹了Java for循環(huán)性能優(yōu)化實現(xiàn)解析,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-01-01
使用java實現(xiàn)備份和恢復(fù)SQLServer表數(shù)據(jù)
這篇文章主要為大家詳細介紹了如何使用java實現(xiàn)備份和恢復(fù)SQLServer表數(shù)據(jù),文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-01-01
Java MongoDB數(shù)據(jù)庫連接方法梳理
MongoDB作為一種介于關(guān)系型數(shù)據(jù)庫和非關(guān)系型數(shù)據(jù)庫之間的產(chǎn)品,它可以提供可擴展的高性能的數(shù)據(jù)存儲解決方案,近些年來受到了開發(fā)者的喜愛2022-08-08
Java操作MongoDB數(shù)據(jù)庫的示例代碼
這篇文章主要介紹了Java操作MongoDB的示例代碼,幫助大家更好的理解和學(xué)習(xí)使用Java,感興趣的朋友可以了解下2021-04-04
Java基于drools做規(guī)則校驗的實現(xiàn)
工作中需要開發(fā)一個規(guī)則服務(wù),提供各種規(guī)則,本文主要介紹了Java基于drools做規(guī)則校驗的實現(xiàn),具有一定的參考價值,感興趣的可以了解一下2024-03-03

