JAVA 多線程編程從基礎(chǔ)原理到實(shí)戰(zhàn)案例
1.1 本章學(xué)習(xí)目標(biāo)與重點(diǎn)
?? 掌握線程的核心概念,理解進(jìn)程與線程的區(qū)別和聯(lián)系。
?? 熟練掌握線程的三種創(chuàng)建方式,理解線程的生命周期及狀態(tài)轉(zhuǎn)換。
?? 掌握線程同步與鎖機(jī)制,解決多線程并發(fā)安全問(wèn)題。
?? 了解線程池的核心原理與使用方法,提升多線程程序性能。
?? 本章重點(diǎn)是 線程同步機(jī)制 和 線程池的實(shí)戰(zhàn)應(yīng)用,這是多線程開(kāi)發(fā)中的核心難點(diǎn)和高頻考點(diǎn)。
1.2 多線程核心概念
1.2.1 進(jìn)程與線程的區(qū)別
?? 進(jìn)程是操作系統(tǒng)進(jìn)行資源分配和調(diào)度的基本單位,每個(gè)進(jìn)程都有獨(dú)立的內(nèi)存空間和系統(tǒng)資源。比如打開(kāi)一個(gè) Java 程序,就會(huì)啟動(dòng)一個(gè)進(jìn)程。
?? 線程是進(jìn)程的執(zhí)行單元,是 CPU 調(diào)度和執(zhí)行的基本單位。一個(gè)進(jìn)程可以包含多個(gè)線程,這些線程共享進(jìn)程的內(nèi)存空間和資源。
| 對(duì)比維度 | 進(jìn)程 | 線程 |
|---|---|---|
| 資源分配 | 擁有獨(dú)立的內(nèi)存空間和資源 | 共享所屬進(jìn)程的內(nèi)存和資源 |
| 開(kāi)銷(xiāo)成本 | 創(chuàng)建和銷(xiāo)毀開(kāi)銷(xiāo)大 | 創(chuàng)建和銷(xiāo)毀開(kāi)銷(xiāo)小 |
| 調(diào)度方式 | 由操作系統(tǒng)內(nèi)核調(diào)度 | 由進(jìn)程內(nèi)部調(diào)度 |
| 獨(dú)立性 | 進(jìn)程之間相互獨(dú)立 | 線程之間共享資源,依賴(lài)性強(qiáng) |
? 核心結(jié)論:線程是輕量級(jí)的進(jìn)程,多線程編程可以充分利用 CPU 資源,提升程序的并發(fā)執(zhí)行效率。
1.2.2 為什么需要多線程
在單線程程序中,代碼是串行執(zhí)行的,當(dāng)遇到耗時(shí)操作(如文件讀寫(xiě)、網(wǎng)絡(luò)請(qǐng)求)時(shí),程序會(huì)阻塞等待,造成 CPU 資源浪費(fèi)。
多線程的優(yōu)勢(shì)體現(xiàn)在以下場(chǎng)景:
- 后臺(tái)任務(wù)處理:如電商系統(tǒng)的訂單超時(shí)取消、數(shù)據(jù)同步等。
- 異步操作:如用戶注冊(cè)后發(fā)送短信驗(yàn)證碼,不阻塞主流程。
- 提高 CPU 利用率:如計(jì)算密集型任務(wù),多線程可以充分利用多核 CPU。
1.3 線程的創(chuàng)建方式
1.3.1 方式1:繼承 Thread 類(lèi)
實(shí)現(xiàn)步驟
① ?? 定義一個(gè)類(lèi)繼承 Thread 類(lèi)。
② ?? 重寫(xiě) run() 方法,編寫(xiě)線程執(zhí)行的業(yè)務(wù)邏輯。
③ ?? 創(chuàng)建線程對(duì)象,調(diào)用 start() 方法啟動(dòng)線程。
代碼實(shí)操
/**
* 繼承Thread類(lèi)創(chuàng)建線程
*/
public class MyThread extends Thread {
// 線程名稱(chēng)
private String threadName;
public MyThread(String threadName) {
this.threadName = threadName;
}
// 重寫(xiě)run方法,定義線程執(zhí)行邏輯
@Override
public void run() {
for (int i = 1; i <= 5; i++) {
System.out.println(threadName + " 執(zhí)行:" + i);
// 模擬線程執(zhí)行耗時(shí)操作
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
// 創(chuàng)建線程對(duì)象
MyThread thread1 = new MyThread("線程A");
MyThread thread2 = new MyThread("線程B");
// 啟動(dòng)線程:注意是調(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
?? 注意事項(xiàng):?jiǎn)?dòng)線程必須調(diào)用 start() 方法,直接調(diào)用 run() 方法會(huì)變成普通方法的串行執(zhí)行。
1.3.2 方式2:實(shí)現(xiàn) Runnable 接口
實(shí)現(xiàn)步驟
① ?? 定義一個(gè)類(lèi)實(shí)現(xiàn) Runnable 接口。
② ?? 重寫(xiě) run() 方法,編寫(xiě)線程執(zhí)行邏輯。
③ ?? 創(chuàng)建 Runnable 實(shí)現(xiàn)類(lèi)對(duì)象,作為 Thread 構(gòu)造方法的參數(shù)。
④ ?? 調(diào)用 start() 方法啟動(dòng)線程。
代碼實(shí)操
/**
* 實(shí)現(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ù)對(duì)象
MyRunnable task1 = new MyRunnable("任務(wù)A");
MyRunnable task2 = new MyRunnable("任務(wù)B");
// 創(chuàng)建線程對(duì)象,傳入任務(wù)
Thread thread1 = new Thread(task1);
Thread thread2 = new Thread(task2);
// 啟動(dòng)線程
thread1.start();
thread2.start();
}
}方式2的優(yōu)勢(shì)
- 避免單繼承的局限性:Java 是單繼承機(jī)制,繼承
Thread類(lèi)后無(wú)法再繼承其他類(lèi),而實(shí)現(xiàn)Runnable接口可以。 - 資源共享:多個(gè)線程可以共享同一個(gè)
Runnable任務(wù)對(duì)象,適合多線程處理同一份資源的場(chǎng)景。
資源共享案例:多線程售票
/**
* 多線程共享資源:模擬售票系統(tǒng)
*/
public class TicketRunnable implements Runnable {
// 共享資源:10張票
private int ticketNum = 10;
@Override
public void run() {
while (ticketNum > 0) {
// 模擬售票耗時(shí)
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 售出第 " + ticketNum-- + " 張票");
}
}
public static void main(String[] args) {
// 一個(gè)任務(wù)對(duì)象,多個(gè)線程共享
TicketRunnable ticketTask = new TicketRunnable();
// 三個(gè)線程模擬三個(gè)售票窗口
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é)果(存在線程安全問(wèn)題,后面會(huì)解決)
窗口1 售出第 10 張票
窗口2 售出第 9 張票
窗口3 售出第 8 張票
窗口1 售出第 7 張票
窗口2 售出第 6 張票
窗口3 售出第 5 張票
窗口1 售出第 4 張票
窗口2 售出第 3 張票
窗口3 售出第 2 張票
窗口1 售出第 1 張票
1.3.3 方式3:實(shí)現(xiàn) Callable 接口(帶返回值)
實(shí)現(xiàn)步驟
① ?? 定義一個(gè)類(lèi)實(shí)現(xiàn) Callable<V> 接口,V 是返回值類(lèi)型。
② ?? 重寫(xiě) call() 方法,編寫(xiě)線程執(zhí)行邏輯,該方法可以拋出異常。
③ ?? 創(chuàng)建 Callable 實(shí)現(xiàn)類(lèi)對(duì)象,通過(guò) FutureTask 包裝。
④ ?? 將 FutureTask 作為 Thread 構(gòu)造方法參數(shù),啟動(dòng)線程。
⑤ ?? 調(diào)用 FutureTask 的 get() 方法獲取返回值。
代碼實(shí)操
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
/**
* 實(shí)現(xiàn)Callable接口創(chuàng)建線程(帶返回值)
*/
public class MyCallable implements Callable<Integer> {
// 計(jì)算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("計(jì)算中:" + i + ",當(dāng)前和為:" + 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)建線程并啟動(dòng)
Thread thread = new Thread(futureTask);
thread.start();
// 獲取返回值:get()方法會(huì)阻塞,直到線程執(zhí)行完成
Integer result = futureTask.get();
System.out.println("1到10的累加和為:" + result);
}
}輸出結(jié)果
計(jì)算中:1,當(dāng)前和為:1
計(jì)算中:2,當(dāng)前和為:3
計(jì)算中:3,當(dāng)前和為:6
計(jì)算中:4,當(dāng)前和為:10
計(jì)算中:5,當(dāng)前和為:15
計(jì)算中:6,當(dāng)前和為:21
計(jì)算中:7,當(dāng)前和為:28
計(jì)算中:8,當(dāng)前和為:36
計(jì)算中:9,當(dāng)前和為:45
計(jì)算中:10,當(dāng)前和為:55
1到10的累加和為:55
? 核心結(jié)論:三種創(chuàng)建方式對(duì)比
| 創(chuàng)建方式 | 優(yōu)點(diǎn) | 缺點(diǎn) |
|---|---|---|
| 繼承 Thread | 編程簡(jiǎn)單,直接使用 this 獲取線程 | 單繼承局限,無(wú)法共享資源 |
| 實(shí)現(xiàn) Runnable | 避免單繼承,支持資源共享 | 無(wú)返回值,無(wú)法拋出受檢異常 |
| 實(shí)現(xiàn) Callable | 有返回值,支持異常拋出 | 編程相對(duì)復(fù)雜 |
1.4 線程的生命周期與狀態(tài)轉(zhuǎn)換
1.4.1 線程的六種狀態(tài)
Java 中線程的生命周期包含六種狀態(tài),定義在 Thread.State 枚舉中:
- NEW(新建狀態(tài)):線程對(duì)象已創(chuàng)建,但未調(diào)用
start()方法。 - RUNNABLE(可運(yùn)行狀態(tài)):調(diào)用
start()方法后,線程處于就緒或運(yùn)行中。- 就緒狀態(tài):線程等待 CPU 調(diào)度。
- 運(yùn)行狀態(tài):線程獲取 CPU 資源,執(zhí)行
run()方法。
- BLOCKED(阻塞狀態(tài)):線程等待獲取鎖資源時(shí)進(jìn)入該狀態(tài)。
- WAITING(等待狀態(tài)):線程調(diào)用
wait()、join()等方法后進(jìn)入,需要被其他線程喚醒。 - TIMED_WAITING(超時(shí)等待狀態(tài)):線程調(diào)用
sleep(long)、wait(long)等方法后進(jìn)入,超時(shí)自動(dòng)喚醒。 - TERMINATED(終止?fàn)顟B(tài)):線程執(zhí)行完畢或異常終止。
1.4.2 線程狀態(tài)轉(zhuǎn)換圖(核心流程)
NEW → RUNNABLE:調(diào)用 start() 方法 RUNNABLE → BLOCKED:競(jìng)爭(zhēng)鎖失敗 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:超時(shí)自動(dòng)喚醒或被其他線程喚醒 RUNNABLE → TERMINATED:run() 方法執(zhí)行完畢或異常終止
1.4.3 常用線程控制方法
| 方法 | 作用 | 注意事項(xiàng) |
|---|---|---|
start() | 啟動(dòng)線程 | 一個(gè)線程只能調(diào)用一次 |
sleep(long millis) | 讓線程休眠指定時(shí)間 | 不會(huì)釋放持有的鎖資源 |
wait() | 讓線程進(jìn)入等待狀態(tài) | 必須在同步代碼塊中調(diào)用,會(huì)釋放鎖 |
notify() | 喚醒一個(gè)等待的線程 | 必須在同步代碼塊中調(diào)用 |
notifyAll() | 喚醒所有等待的線程 | 必須在同步代碼塊中調(diào)用 |
join() | 等待該線程執(zhí)行完畢 | 可以實(shí)現(xiàn)線程的順序執(zhí)行 |
代碼實(shí)操:線程的順序執(zhí)行(join 方法)
/**
* 使用join方法實(shí)現(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();
}
}
});
// 啟動(dòng)線程A
threadA.start();
// 等待線程A執(zhí)行完畢,再啟動(dòng)線程B
threadA.join();
threadB.start();
}
}輸出結(jié)果(線程A執(zhí)行完才會(huì)執(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ā)安全問(wèn)題的產(chǎn)生
當(dāng)多個(gè)線程同時(shí)操作共享資源時(shí),會(huì)導(dǎo)致數(shù)據(jù)不一致的問(wèn)題。比如前面的售票案例,在極端情況下會(huì)出現(xiàn)超賣(mài)或重復(fù)售票的問(wèn)題。
問(wèn)題復(fù)現(xiàn):多線程售票的并發(fā)安全問(wèn)題
public class UnsafeTicketDemo implements Runnable {
private int ticketNum = 10;
@Override
public void run() {
while (ticketNum > 0) {
// 模擬網(wǎng)絡(luò)延遲,放大并發(fā)問(wèn)題
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 核心問(wèn)題:判斷和操作不是原子性的
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)的錯(cuò)誤結(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號(hào)票的問(wèn)題,這就是典型的并發(fā)安全問(wèn)題。
1.5.2 解決方案1:同步代碼塊(synchronized)
?? 同步代碼塊的核心是鎖機(jī)制,通過(guò) synchronized 關(guān)鍵字鎖定一個(gè)對(duì)象,保證同一時(shí)間只有一個(gè)線程能執(zhí)行代碼塊中的內(nèi)容。
語(yǔ)法格式
synchronized (鎖對(duì)象) {
// 需要同步的代碼(操作共享資源的代碼)
}
代碼實(shí)操:同步代碼塊解決售票問(wèn)題
public class SafeTicketDemo1 implements Runnable {
private int ticketNum = 10;
// 定義鎖對(duì)象:必須是多個(gè)線程共享的對(duì)象
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é)果(無(wú)重復(fù)、無(wú)超賣(mài),解決并發(fā)安全問(wèn)題)
窗口1 售出第 10 張票
窗口1 售出第 9 張票
窗口2 售出第 8 張票
窗口2 售出第 7 張票
窗口3 售出第 6 張票
窗口3 售出第 5 張票
窗口1 售出第 4 張票
窗口1 售出第 3 張票
窗口2 售出第 2 張票
窗口2 售出第 1 張票
?? 注意事項(xiàng):鎖對(duì)象必須是多個(gè)線程共享的對(duì)象,否則無(wú)法實(shí)現(xiàn)同步效果。
1.5.3 解決方案2:同步方法(synchronized)
?? 同步方法是將 synchronized 關(guān)鍵字修飾在方法上,等價(jià)于鎖定當(dāng)前對(duì)象(this)。
語(yǔ)法格式
public synchronized 返回值類(lèi)型 方法名(參數(shù)列表) {
// 需要同步的代碼
}
代碼實(shí)操:同步方法解決售票問(wèn)題
public class SafeTicketDemo2 implements Runnable {
private int ticketNum = 10;
@Override
public void run() {
while (true) {
if (!sellTicket()) {
break;
}
}
}
// 同步方法:鎖定的是this對(duì)象
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 新增的鎖機(jī)制,相比 synchronized 更加靈活,可以手動(dòng)控制鎖的獲取和釋放。
常用實(shí)現(xiàn)類(lèi)是 ReentrantLock(可重入鎖)。
實(shí)現(xiàn)步驟
① ?? 創(chuàng)建 ReentrantLock 對(duì)象。
② ?? 在需要同步的代碼前調(diào)用 lock() 方法獲取鎖。
③ ?? 在 finally 塊中調(diào)用 unlock() 方法釋放鎖,確保鎖一定會(huì)被釋放。
代碼實(shí)操:Lock 鎖解決售票問(wèn)題
import java.util.concurrent.locks.ReentrantLock;
public class SafeTicketDemo3 implements Runnable {
private int ticketNum = 10;
// 創(chuàng)建可重入鎖對(duì)象
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 的對(duì)比
| 特性 | synchronized | Lock |
|---|---|---|
| 鎖獲取釋放 | 自動(dòng)獲取和釋放 | 手動(dòng)調(diào)用 lock()/unlock() |
| 靈活性 | 低 | 高(支持公平鎖/非公平鎖) |
| 可中斷性 | 不支持 | 支持 |
| 條件變量 | 不支持 | 支持(Condition) |
1.6 線程池:高效管理多線程
1.6.1 為什么需要線程池
頻繁創(chuàng)建和銷(xiāo)毀線程會(huì)消耗大量系統(tǒng)資源,線程池可以復(fù)用線程,降低系統(tǒng)開(kāi)銷(xiāo)。線程池的核心優(yōu)勢(shì):
- 降低資源消耗:復(fù)用已創(chuàng)建的線程,減少創(chuàng)建和銷(xiāo)毀線程的開(kāi)銷(xiāo)。
- 提高響應(yīng)速度:任務(wù)到達(dá)時(shí),無(wú)需等待線程創(chuàng)建即可立即執(zhí)行。
- 便于線程管理:可以控制最大并發(fā)數(shù),避免線程過(guò)多導(dǎo)致資源耗盡。
1.6.2 線程池的核心參數(shù)(ThreadPoolExecutor)
Java 中線程池的核心實(shí)現(xiàn)類(lèi)是 ThreadPoolExecutor,其構(gòu)造方法包含 7 個(gè)核心參數(shù):
public ThreadPoolExecutor(
int corePoolSize, // 核心線程數(shù)
int maximumPoolSize, // 最大線程數(shù)
long keepAliveTime, // 非核心線程空閑存活時(shí)間
TimeUnit unit, // 時(shí)間單位
BlockingQueue<Runnable> workQueue, // 任務(wù)隊(duì)列
ThreadFactory threadFactory, // 線程工廠
RejectedExecutionHandler handler // 拒絕策略
)
參數(shù)說(shuō)明
- 核心線程數(shù):線程池長(zhǎng)期保持的線程數(shù)量,即使空閑也不會(huì)銷(xiāo)毀。
- 最大線程數(shù):線程池允許創(chuàng)建的最大線程數(shù)。
- 空閑存活時(shí)間:非核心線程空閑超過(guò)該時(shí)間會(huì)被銷(xiāo)毀。
- 任務(wù)隊(duì)列:存放等待執(zhí)行的任務(wù),當(dāng)核心線程都在忙時(shí),任務(wù)會(huì)進(jìn)入隊(duì)列。
- 拒絕策略:當(dāng)任務(wù)隊(duì)列滿且線程數(shù)達(dá)到最大時(shí),如何處理新任務(wù)。
1.6.3 線程池的使用方式
方式1:通過(guò) Executors 工具類(lèi)創(chuàng)建(快速使用)
Executors 提供了幾種常用的線程池創(chuàng)建方法:
newFixedThreadPool(int nThreads):固定大小的線程池。newCachedThreadPool():緩存線程池,線程數(shù)可動(dòng)態(tài)調(diào)整。newSingleThreadExecutor():?jiǎn)尉€程池,保證任務(wù)順序執(zhí)行。
代碼實(shí)操:固定大小線程池
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個(gè)任務(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個(gè)線程復(fù)用執(zhí)行10個(gè)任務(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:手動(dòng)創(chuàng)建 ThreadPoolExecutor(推薦,更可控)
import java.util.concurrent.*;
/**
* 手動(dòng)創(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ù)隊(duì)列:容量為3的阻塞隊(duì)列
BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(3);
// 線程工廠:默認(rèn)線程工廠
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é)果(當(dāng)任務(wù)數(shù)超過(guò) 最大線程數(shù)+隊(duì)列容量=8 時(shí),新任務(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]
?? 注意事項(xiàng):實(shí)際開(kāi)發(fā)中推薦手動(dòng)創(chuàng)建線程池,避免使用 Executors 工具類(lèi)的默認(rèn)方法,防止資源耗盡。
1.7 實(shí)戰(zhàn)案例:多線程實(shí)現(xiàn)文件批量下載
1.7.1 需求分析
?? 實(shí)現(xiàn)一個(gè)多線程文件下載工具,支持從指定 URL 列表批量下載文件,要求:
- 使用線程池管理下載線程,控制并發(fā)數(shù)。
- 記錄每個(gè)文件的下載狀態(tài)(成功/失?。?。
- 支持?jǐn)帱c(diǎn)續(xù)傳(可選)。
1.7.2 代碼實(shí)現(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<>();
/**
* 下載單個(gè)文件
* @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);
// 讀取文件流并寫(xiě)入本地
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) {
// 測(cè)試URL列表(替換為實(shí)際可下載的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盤(pán)download目錄
batchDownload(urlList, "D:\\download");
}
}1.7.3 案例總結(jié)
? 這個(gè)多線程下載工具綜合運(yùn)用了線程池、同步機(jī)制、IO流等知識(shí),核心亮點(diǎn):
- 使用線程池控制并發(fā)數(shù),避免線程過(guò)多導(dǎo)致系統(tǒng)資源耗盡。
- 使用
synchronized保證下載狀態(tài)列表的線程安全。 - 采用
CallerRunsPolicy拒絕策略,當(dāng)任務(wù)過(guò)多時(shí),由調(diào)用線程執(zhí)行,避免任務(wù)丟失。 - 完善的資源關(guān)閉和異常處理,保證程序健壯性。
1.8 本章總結(jié)
- 線程是進(jìn)程的執(zhí)行單元,多線程可以提升程序并發(fā)效率,充分利用 CPU 資源。
- 線程有三種創(chuàng)建方式:繼承 Thread、實(shí)現(xiàn) Runnable、實(shí)現(xiàn) Callable,推薦使用后兩種。
- 線程的生命周期包含六種狀態(tài),核心是掌握狀態(tài)之間的轉(zhuǎn)換條件。
- 并發(fā)安全問(wèn)題的根源是多個(gè)線程操作共享資源,解決方案有 synchronized 同步代碼塊/方法、Lock 鎖。
- 線程池可以復(fù)用線程,降低系統(tǒng)開(kāi)銷(xiāo),實(shí)際開(kāi)發(fā)中推薦手動(dòng)創(chuàng)建 ThreadPoolExecutor。
- 多線程編程的核心是線程同步和資源管理,要注意避免死鎖、內(nèi)存可見(jiàn)性等問(wèn)題。
到此這篇關(guān)于JAVA 多線程編程從基礎(chǔ)原理到實(shí)戰(zhàn)案例的文章就介紹到這了,更多相關(guān)java多線程編程內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JAVA多線程的使用場(chǎng)景與注意事項(xiàng)總結(jié)
這篇文章主要給大家介紹了關(guān)于JAVA多線程的使用場(chǎng)景與注意事項(xiàng)的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者使用java具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-03-03
OPENCV+JAVA實(shí)現(xiàn)人臉識(shí)別
這篇文章主要為大家詳細(xì)介紹了OPENCV+JAVA實(shí)現(xiàn)人臉識(shí)別,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-02-02
java easyUI實(shí)現(xiàn)自定義網(wǎng)格視圖實(shí)例代碼
這篇文章主要給大家介紹了關(guān)于java easyUI實(shí)現(xiàn)自定義網(wǎng)格視圖的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-10-10
Java xml出現(xiàn)錯(cuò)誤 javax.xml.transform.TransformerException: java.
這篇文章主要介紹了Java xml出現(xiàn)錯(cuò)誤 javax.xml.transform.TransformerException: java.lang.NullPointerException的相關(guān)資料,需要的朋友可以參考下2016-11-11
Liquibase結(jié)合SpringBoot使用實(shí)現(xiàn)數(shù)據(jù)庫(kù)管理功能
Liquibase 是一個(gè)強(qiáng)大的數(shù)據(jù)庫(kù)管理工具,它幫助你通過(guò)自動(dòng)化管理數(shù)據(jù)庫(kù)的變更、版本控制、和回滾,簡(jiǎn)化了開(kāi)發(fā)中的數(shù)據(jù)庫(kù)遷移工作,這篇文章主要介紹了Liquibase結(jié)合SpringBoot使用實(shí)現(xiàn)數(shù)據(jù)庫(kù)管理,需要的朋友可以參考下2024-12-12
利用Java實(shí)現(xiàn)和可被K整除的子數(shù)組完整實(shí)例
這篇文章主要給大家介紹了關(guān)于利用Java實(shí)現(xiàn)和可被K整除的子數(shù)組的相關(guān)資料,這道題來(lái)自力扣,通過(guò)學(xué)習(xí)這道題的解題思路以及代碼對(duì)大家的學(xué)習(xí)或者工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2024-01-01
實(shí)例詳解MyBatis-plus自動(dòng)填充功能
每次對(duì)數(shù)據(jù)進(jìn)行新增、刪除、修改時(shí)都需要對(duì)這些字段進(jìn)行設(shè)置,雖然新增時(shí)間和修改時(shí)間可以使用數(shù)據(jù)庫(kù)的時(shí)間,但是新增人和修改人就不能使用這樣的功能,下面小編給大家介紹下MyBatis-plus自動(dòng)填充功能的實(shí)例代碼,感興趣的朋友一起看看吧2022-01-01

