最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

JAVA 多線程編程從基礎(chǔ)原理到實(shí)戰(zhàn)案例

 更新時(shí)間:2026年03月17日 10:48:53   作者:星河耀銀海  
本章介紹了線程的核心概念、創(chuàng)建方式、生命周期、同步與并發(fā)安全以及線程池的使用,通過(guò)案例分析,展示了如何使用線程池進(jìn)行文件批量下載,結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧

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)景:

  1. 后臺(tái)任務(wù)處理:如電商系統(tǒng)的訂單超時(shí)取消、數(shù)據(jù)同步等。
  2. 異步操作:如用戶注冊(cè)后發(fā)送短信驗(yàn)證碼,不阻塞主流程。
  3. 提高 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ì)

  1. 避免單繼承的局限性:Java 是單繼承機(jī)制,繼承 Thread 類(lèi)后無(wú)法再繼承其他類(lèi),而實(shí)現(xiàn) Runnable 接口可以。
  2. 資源共享:多個(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)用 FutureTaskget() 方法獲取返回值。

代碼實(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 枚舉中:

  1. NEW(新建狀態(tài)):線程對(duì)象已創(chuàng)建,但未調(diào)用 start() 方法。
  2. RUNNABLE(可運(yùn)行狀態(tài)):調(diào)用 start() 方法后,線程處于就緒或運(yùn)行中。
    • 就緒狀態(tài):線程等待 CPU 調(diào)度。
    • 運(yùn)行狀態(tài):線程獲取 CPU 資源,執(zhí)行 run() 方法。
  3. BLOCKED(阻塞狀態(tài)):線程等待獲取鎖資源時(shí)進(jìn)入該狀態(tài)。
  4. WAITING(等待狀態(tài)):線程調(diào)用 wait()、join() 等方法后進(jìn)入,需要被其他線程喚醒。
  5. TIMED_WAITING(超時(shí)等待狀態(tài)):線程調(diào)用 sleep(long)wait(long) 等方法后進(jìn)入,超時(shí)自動(dòng)喚醒。
  6. 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é)論:synchronizedLock 的對(duì)比

特性synchronizedLock
鎖獲取釋放自動(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ì):

  1. 降低資源消耗:復(fù)用已創(chuàng)建的線程,減少創(chuàng)建和銷(xiāo)毀線程的開(kāi)銷(xiāo)。
  2. 提高響應(yīng)速度:任務(wù)到達(dá)時(shí),無(wú)需等待線程創(chuàng)建即可立即執(zhí)行。
  3. 便于線程管理:可以控制最大并發(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ō)明

  1. 核心線程數(shù):線程池長(zhǎng)期保持的線程數(shù)量,即使空閑也不會(huì)銷(xiāo)毀。
  2. 最大線程數(shù):線程池允許創(chuàng)建的最大線程數(shù)。
  3. 空閑存活時(shí)間:非核心線程空閑超過(guò)該時(shí)間會(huì)被銷(xiāo)毀。
  4. 任務(wù)隊(duì)列:存放等待執(zhí)行的任務(wù),當(dāng)核心線程都在忙時(shí),任務(wù)會(huì)進(jìn)入隊(duì)列。
  5. 拒絕策略:當(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 列表批量下載文件,要求:

  1. 使用線程池管理下載線程,控制并發(fā)數(shù)。
  2. 記錄每個(gè)文件的下載狀態(tài)(成功/失?。?。
  3. 支持?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):

  1. 使用線程池控制并發(fā)數(shù),避免線程過(guò)多導(dǎo)致系統(tǒng)資源耗盡。
  2. 使用 synchronized 保證下載狀態(tài)列表的線程安全。
  3. 采用 CallerRunsPolicy 拒絕策略,當(dāng)任務(wù)過(guò)多時(shí),由調(diào)用線程執(zhí)行,避免任務(wù)丟失。
  4. 完善的資源關(guān)閉和異常處理,保證程序健壯性。

1.8 本章總結(jié)

  1. 線程是進(jìn)程的執(zhí)行單元,多線程可以提升程序并發(fā)效率,充分利用 CPU 資源。
  2. 線程有三種創(chuàng)建方式:繼承 Thread、實(shí)現(xiàn) Runnable、實(shí)現(xiàn) Callable,推薦使用后兩種。
  3. 線程的生命周期包含六種狀態(tài),核心是掌握狀態(tài)之間的轉(zhuǎn)換條件。
  4. 并發(fā)安全問(wèn)題的根源是多個(gè)線程操作共享資源,解決方案有 synchronized 同步代碼塊/方法、Lock 鎖。
  5. 線程池可以復(fù)用線程,降低系統(tǒng)開(kāi)銷(xiāo),實(shí)際開(kāi)發(fā)中推薦手動(dòng)創(chuàng)建 ThreadPoolExecutor。
  6. 多線程編程的核心是線程同步資源管理,要注意避免死鎖、內(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é)

    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í)別

    OPENCV+JAVA實(shí)現(xiàn)人臉識(shí)別

    這篇文章主要為大家詳細(xì)介紹了OPENCV+JAVA實(shí)現(xiàn)人臉識(shí)別,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-02-02
  • java限流算法詳細(xì)

    java限流算法詳細(xì)

    這篇文章詳細(xì)的介紹了java限流算法常用到的算法計(jì)數(shù)算法、漏桶算法、令牌桶等算法的相關(guān)資料,需要的朋友可以參考下文,希望本篇文章能幫助到您
    2021-09-09
  • RestTemplate返回值中文亂碼問(wèn)題

    RestTemplate返回值中文亂碼問(wèn)題

    這篇文章主要介紹了RestTemplate返回值中文亂碼問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-04-04
  • java easyUI實(shí)現(xiàn)自定義網(wǎng)格視圖實(shí)例代碼

    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中的抽象類(lèi)和接口定義與用法詳解

    java中的抽象類(lèi)和接口定義與用法詳解

    在面向?qū)ο蟮母拍钪校械膶?duì)象都是通過(guò)類(lèi)來(lái)描繪的,但是反過(guò)來(lái),并不是所有的類(lèi)都是用來(lái)描繪對(duì)象的,如果一個(gè)類(lèi)中沒(méi)有包含足夠的信息來(lái)描繪一個(gè)具體的對(duì)象,這樣的類(lèi)就是抽象類(lèi)
    2021-10-10
  • Java xml出現(xiàn)錯(cuò)誤 javax.xml.transform.TransformerException: java.lang.NullPointerException

    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結(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í)例

    利用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)填充功能

    實(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

最新評(píng)論

绵阳市| 兰州市| 遵化市| 永昌县| 广东省| 牙克石市| 孙吴县| 江北区| 新昌县| 黄陵县| 奇台县| 水城县| 江达县| 宁晋县| 建宁县| 独山县| 鲁甸县| 南雄市| 平邑县| 镇江市| 溧水县| 甘洛县| 新津县| 平乐县| 盱眙县| 杭锦旗| 湛江市| 南开区| 丹寨县| 广宗县| 台前县| 乌鲁木齐县| 阳城县| 颍上县| 清丰县| 牟定县| 栾城县| 辉南县| 北安市| 崇明县| 德安县|