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

Java多線程核心機(jī)制全面解析

 更新時(shí)間:2025年11月18日 16:19:36   作者:故淵ZY  
本文全面剖析了Java多線程的設(shè)計(jì)與應(yīng)用,從多線程的本質(zhì)、生命周期、核心機(jī)制到實(shí)戰(zhàn)最佳實(shí)踐,幫助讀者掌握并發(fā)編程的核心能力,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧

Java 多線程簡(jiǎn)介

在計(jì)算機(jī)硬件進(jìn)入多核時(shí)代后,單線程程序已無(wú)法充分利用 CPU 資源。多線程(Multithreading)作為并發(fā)編程的核心技術(shù),允許程序在同一進(jìn)程內(nèi)同時(shí)執(zhí)行多個(gè)任務(wù),是提升程序吞吐量、降低響應(yīng)延遲的關(guān)鍵。從簡(jiǎn)單的后臺(tái)任務(wù)到復(fù)雜的分布式系統(tǒng),多線程無(wú)處不在。本文將從多線程的本質(zhì)、生命周期、核心機(jī)制到實(shí)戰(zhàn)最佳實(shí)踐,全面剖析 Java 多線程的設(shè)計(jì)與應(yīng)用,幫你掌握并發(fā)編程的核心能力。

一、多線程的本質(zhì):為什么需要多線程?

1. 多線程與進(jìn)程、線程的關(guān)系

要理解多線程,首先需要明確進(jìn)程(Process) 與線程(Thread) 的區(qū)別與聯(lián)系 —— 二者是操作系統(tǒng)中調(diào)度和資源分配的基本單位,但定位不同:

對(duì)比維度進(jìn)程(Process)線程(Thread)
資源分配操作系統(tǒng)資源分配的基本單位(獨(dú)立內(nèi)存空間、文件句柄)進(jìn)程內(nèi)的執(zhí)行單元,共享進(jìn)程資源(堆、方法區(qū)),僅擁有獨(dú)立棧和程序計(jì)數(shù)器
切換開(kāi)銷高(需切換內(nèi)存映射、寄存器狀態(tài),耗時(shí)約 10-100 微秒)低(僅切換棧和程序計(jì)數(shù)器,耗時(shí)約 1-10 微秒)
通信方式復(fù)雜(需通過(guò) IPC:管道、socket、共享內(nèi)存)簡(jiǎn)單(直接訪問(wèn)共享變量,需同步控制)
數(shù)量限制少(系統(tǒng)能同時(shí)運(yùn)行的進(jìn)程數(shù)通常不超過(guò)千級(jí))多(單個(gè)進(jìn)程可創(chuàng)建數(shù)千個(gè)線程)

形象類比:進(jìn)程是 “工廠”,擁有獨(dú)立的廠房(內(nèi)存空間)和設(shè)備(文件句柄);線程是 “工廠里的工人”,共享廠房和設(shè)備,同時(shí)有自己的工具(??臻g),多個(gè)工人可同時(shí)處理不同任務(wù)(并發(fā)執(zhí)行)。

多線程的核心價(jià)值在于 **“在單個(gè)進(jìn)程內(nèi)實(shí)現(xiàn)并發(fā)”**—— 無(wú)需創(chuàng)建多個(gè)獨(dú)立進(jìn)程,即可利用多核 CPU 同時(shí)執(zhí)行任務(wù),大幅降低資源開(kāi)銷和通信成本。

2. 多線程解決的核心問(wèn)題

多線程并非 “銀彈”,但能高效解決兩類典型問(wèn)題,這也是它被廣泛應(yīng)用的原因:

(1)CPU 密集型任務(wù):充分利用多核 CPU

CPU 密集型任務(wù)(如數(shù)據(jù)計(jì)算、排序、加密)的核心瓶頸是 CPU 算力。單線程只能利用一個(gè) CPU 核心,而多線程可將任務(wù)拆分到多個(gè)核心并行執(zhí)行,減少總耗時(shí)。示例:用 4 核 CPU 處理 4 個(gè)排序任務(wù),單線程需 4 秒(串行執(zhí)行),多線程僅需 1 秒(并行執(zhí)行),理論效率提升 300%。

(2)IO 密集型任務(wù):減少 CPU 空閑時(shí)間

IO 密集型任務(wù)(如網(wǎng)絡(luò)請(qǐng)求、文件讀寫(xiě)、數(shù)據(jù)庫(kù)操作)的核心瓶頸是 IO 等待(CPU 等待數(shù)據(jù)就緒,如等待網(wǎng)絡(luò)響應(yīng)、磁盤 IO 完成)。此時(shí) CPU 處于空閑狀態(tài),多線程可讓 CPU 在 IO 等待期間處理其他任務(wù),提升 CPU 利用率。示例:?jiǎn)尉€程處理 10 個(gè)網(wǎng)絡(luò)請(qǐng)求(每個(gè)請(qǐng)求耗時(shí) 1 秒,其中 0.8 秒是 IO 等待),總耗時(shí) 10 秒;多線程處理時(shí),CPU 在 IO 等待期間切換線程,總耗時(shí)可降至 1 秒左右,CPU 利用率從 20% 提升至 100%。

3. Java 多線程的特殊性

Java 多線程是內(nèi)核級(jí)線程(1:1 映射模型),即每個(gè) Java 線程對(duì)應(yīng)一個(gè)操作系統(tǒng)內(nèi)核線程:

  • 創(chuàng)建 Java 線程時(shí),JVM 會(huì)調(diào)用操作系統(tǒng) API(如 Linux 的pthread_create)創(chuàng)建內(nèi)核線程;
  • 線程的調(diào)度(CPU 時(shí)間片分配、線程切換)由操作系統(tǒng)內(nèi)核完成,JVM 僅負(fù)責(zé)線程的創(chuàng)建、銷毀和狀態(tài)管理;
  • 相比用戶級(jí)線程(如 Python 的 GIL 鎖限制),Java 多線程能真正利用多核 CPU,但線程數(shù)量受限于操作系統(tǒng)(如 Linux 默認(rèn)單個(gè)進(jìn)程可創(chuàng)建的線程數(shù)上限約為 1000-10000,取決于內(nèi)存大小)。

二、Java 多線程的生命周期:6 種狀態(tài)與狀態(tài)轉(zhuǎn)換

Java 線程的生命周期被嚴(yán)格定義在java.lang.Thread.State枚舉中,包含 6 種狀態(tài)。線程在不同狀態(tài)間的轉(zhuǎn)換遵循固定規(guī)則,是理解線程調(diào)度和并發(fā)控制的基礎(chǔ)。

1. 6 種核心狀態(tài)及含義

狀態(tài)名稱核心含義觸發(fā)場(chǎng)景
NEW(新建)線程對(duì)象已創(chuàng)建(如new Thread()),但未調(diào)用start(),未與內(nèi)核線程關(guān)聯(lián)。Thread thread = new Thread();
**RUNNABLE(可運(yùn)行)線程已調(diào)用start(),分為 “就緒”(等待 CPU 調(diào)度)和 “運(yùn)行中”(占用 CPU)兩種子狀態(tài),JVM 不區(qū)分這兩種子狀態(tài)。thread.start();后,線程等待 CPU 調(diào)度或正在執(zhí)行run()方法
**BLOCKED(阻塞)線程等待獲取synchronized鎖(進(jìn)入同步塊 / 方法時(shí)未搶到鎖),釋放 CPU,等待鎖釋放。線程 A 執(zhí)行synchronized方法時(shí),線程 B 嘗試進(jìn)入該方法
**WAITING(無(wú)限等待)線程通過(guò)wait()、join()LockSupport.park()主動(dòng)放棄 CPU,無(wú)限期等待其他線程喚醒。object.wait()(未指定超時(shí))、thread.join()LockSupport.park()
**TIMED_WAITING(計(jì)時(shí)等待)線程通過(guò)wait(long)、sleep(long)等方法放棄 CPU,等待指定時(shí)間后自動(dòng)喚醒,或被其他線程提前喚醒。Thread.sleep(1000)、object.wait(500)LockSupport.parkNanos(1_000_000)
**TERMINATED(終止)線程執(zhí)行完畢(run()方法正常返回)或因未捕獲異常退出,生命周期結(jié)束,無(wú)法再恢復(fù)。run()方法執(zhí)行完畢、線程拋出RuntimeException且未捕獲

2. 狀態(tài)轉(zhuǎn)換流程圖(核心路徑)

NEW → RUNNABLE:調(diào)用thread.start()(僅能觸發(fā)一次,再次調(diào)用會(huì)拋IllegalThreadStateException)
RUNNABLE → BLOCKED:嘗試獲取synchronized鎖失敗
BLOCKED → RUNNABLE:其他線程釋放synchronized鎖,當(dāng)前線程搶到鎖
RUNNABLE → WAITING:調(diào)用object.wait()、thread.join()、LockSupport.park()
WAITING → RUNNABLE:其他線程調(diào)用object.notify()/notifyAll()、LockSupport.unpark()
RUNNABLE → TIMED_WAITING:調(diào)用Thread.sleep(long)、object.wait(long)、thread.join(long)
TIMED_WAITING → RUNNABLE:等待時(shí)間到、其他線程調(diào)用notify()/unpark()
RUNNABLE → TERMINATED:run()執(zhí)行完畢、未捕獲異常終止

關(guān)鍵注意點(diǎn)

  • start()run()的區(qū)別:start()是 “啟動(dòng)線程”(觸發(fā)狀態(tài)從 NEW→RUNNABLE),run()是 “執(zhí)行任務(wù)邏輯”(僅普通方法調(diào)用,不會(huì)創(chuàng)建新線程);
  • sleep()wait()的區(qū)別:sleep()不釋放synchronized鎖(線程仍持有鎖),wait()會(huì)釋放鎖;sleep()Thread類方法,wait()Object類方法(需在synchronized塊中調(diào)用);
  • 終止線程的正確方式:不推薦用stop()(已廢棄,強(qiáng)制終止會(huì)導(dǎo)致資源泄漏),應(yīng)通過(guò)volatile變量控制循環(huán)退出(如while (!stopFlag) { ... })。

三、Java 多線程的創(chuàng)建方式:4 種核心方法對(duì)比

Java 提供了多種創(chuàng)建線程的方式,不同方式在 “靈活性”“返回值”“異常處理” 上存在差異,需根據(jù)場(chǎng)景選擇。

1. 方式 1:繼承 Thread 類(重寫(xiě) run ())

最基礎(chǔ)的創(chuàng)建方式,通過(guò)繼承Thread類并重寫(xiě)run()方法定義線程任務(wù)。

// 1. 繼承Thread類
class MyThread extends Thread {
    // 2. 重寫(xiě)run():定義線程執(zhí)行的任務(wù)(無(wú)返回值,不能拋受檢異常)
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            // Thread.currentThread():獲取當(dāng)前執(zhí)行的線程對(duì)象
            System.out.println(Thread.currentThread().getName() + ": " + i);
            try {
                Thread.sleep(500); // 模擬任務(wù)耗時(shí),線程進(jìn)入TIMED_WAITING狀態(tài)
            } catch (InterruptedException e) {
                // 捕獲中斷異常(如其他線程調(diào)用interrupt())
                System.out.println(Thread.currentThread().getName() + "被中斷");
                return; // 中斷后退出線程
            }
        }
    }
}
// 使用線程
public class ThreadCreate1 {
    public static void main(String[] args) {
        // 3. 創(chuàng)建線程對(duì)象
        MyThread thread1 = new MyThread();
        MyThread thread2 = new MyThread();
        // 4. 設(shè)置線程名(便于調(diào)試)
        thread1.setName("線程A");
        thread2.setName("線程B");
        // 5. 啟動(dòng)線程(狀態(tài)從NEW→RUNNABLE)
        thread1.start();
        thread2.start();
        // 注意:直接調(diào)用run()不會(huì)創(chuàng)建新線程,僅在主線程中執(zhí)行
        // thread1.run(); // 錯(cuò)誤用法
    }
}

輸出結(jié)果(并發(fā)執(zhí)行,順序不固定)

線程A: 0
線程B: 0
線程A: 1
線程B: 1
線程A: 2
線程B: 2
...

優(yōu)缺點(diǎn)

  • 優(yōu)點(diǎn):API 簡(jiǎn)單,直接操作 Thread 對(duì)象(如設(shè)置優(yōu)先級(jí)、中斷);
  • 缺點(diǎn):Java 單繼承限制(若類已繼承其他類,無(wú)法再繼承 Thread),run()無(wú)返回值、不能拋受檢異常。

2. 方式 2:實(shí)現(xiàn) Runnable 接口(推薦)

通過(guò)實(shí)現(xiàn)Runnable接口的run()方法定義任務(wù),再將Runnable對(duì)象傳入Thread構(gòu)造器,避免單繼承限制,是更靈活的方式。

// 1. 實(shí)現(xiàn)Runnable接口
class MyRunnable implements Runnable {
    // 2. 重寫(xiě)run():定義任務(wù)(無(wú)返回值,不能拋受檢異常)
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + ": " + i);
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
// 使用線程
public class ThreadCreate2 {
    public static void main(String[] args) {
        // 3. 創(chuàng)建任務(wù)對(duì)象(任務(wù)與線程分離)
        MyRunnable task = new MyRunnable();
        // 4. 將任務(wù)傳入Thread,啟動(dòng)線程(多個(gè)線程可共享同一個(gè)任務(wù))
        Thread thread1 = new Thread(task, "線程C");
        Thread thread2 = new Thread(task, "線程D");
        thread1.start();
        thread2.start();
    }
}

核心優(yōu)勢(shì)

  • 解除單繼承限制:類可同時(shí)實(shí)現(xiàn)Runnable和其他接口(如Serializable);
  • 任務(wù)與線程分離:一個(gè)Runnable任務(wù)可被多個(gè)線程共享(如多線程執(zhí)行同一任務(wù)),符合 “單一職責(zé)原則”;
  • 靈活組合:可通過(guò)匿名內(nèi)部類或 Lambda 表達(dá)式簡(jiǎn)化代碼(JDK 8+):
    // Lambda表達(dá)式簡(jiǎn)化(無(wú)需定義MyRunnable類)
    Thread thread = new Thread(() -> {
        System.out.println("Lambda線程執(zhí)行");
    }, "Lambda線程");
    thread.start();
    

3. 方式 3:實(shí)現(xiàn) Callable 接口(帶返回值)

Runnable的增強(qiáng)版,支持線程執(zhí)行后返回結(jié)果(通過(guò)Future獲?。┖蛼伋鍪軝z異常,適用于需要獲取任務(wù)結(jié)果的場(chǎng)景(如異步計(jì)算、耗時(shí)任務(wù))。

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
// 1. 實(shí)現(xiàn)Callable接口(泛型指定返回值類型)
class MyCallable implements Callable<Integer> {
    private int num;
    public MyCallable(int num) {
        this.num = num;
    }
    // 2. 重寫(xiě)call():有返回值,可拋受檢異常
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 1; i <= num; i++) {
            sum += i;
            Thread.sleep(100); // 模擬計(jì)算耗時(shí)
        }
        return sum; // 返回1~num的和
    }
}
// 使用線程
public class ThreadCreate3 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 3. 創(chuàng)建Callable任務(wù)對(duì)象
        MyCallable task = new MyCallable(10);
        // 4. 用FutureTask包裝Callable(FutureTask實(shí)現(xiàn)了Runnable,可傳入Thread)
        FutureTask<Integer> futureTask = new FutureTask<>(task);
        // 5. 啟動(dòng)線程
        new Thread(futureTask, "計(jì)算線程").start();
        // 6. 獲取任務(wù)結(jié)果(get()是阻塞方法,直到線程執(zhí)行完畢返回結(jié)果)
        System.out.println("主線程等待計(jì)算結(jié)果...");
        Integer result = futureTask.get(); // 阻塞等待,直到call()返回
        System.out.println("1~10的和:" + result); // 輸出:55
        // 其他FutureTask方法
        System.out.println("任務(wù)是否完成:" + futureTask.isDone()); // true
        System.out.println("任務(wù)是否取消:" + futureTask.isCancelled()); // false
    }
}

核心特性

  • 帶返回值:通過(guò)FutureTask.get()獲取call()的返回值,解決Runnable無(wú)返回值的問(wèn)題;
  • 異常傳遞:call()拋出的異常會(huì)被封裝到ExecutionException中,通過(guò)get()的異常鏈獲?。?/li>
  • 可取消:通過(guò)futureTask.cancel(true)取消任務(wù)(true表示中斷正在執(zhí)行的線程,false表示僅取消未執(zhí)行的任務(wù))。

4. 方式 4:線程池(ExecutorService,實(shí)戰(zhàn)首選)

通過(guò)java.util.concurrent.ExecutorService線程池創(chuàng)建線程,避免頻繁創(chuàng)建 / 銷毀線程的開(kāi)銷(線程池復(fù)用線程),是企業(yè)級(jí)開(kāi)發(fā)的首選方式。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class ThreadPoolCreate {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 1. 創(chuàng)建線程池(根據(jù)場(chǎng)景選擇線程池類型)
        // 固定線程數(shù)線程池(適合CPU密集型任務(wù))
        ExecutorService fixedPool = Executors.newFixedThreadPool(2);
        // 緩存線程池(適合IO密集型任務(wù),線程空閑60秒后銷毀)
        // ExecutorService cachedPool = Executors.newCachedThreadPool();
        // 單線程池(適合順序執(zhí)行任務(wù))
        // ExecutorService singlePool = Executors.newSingleThreadExecutor();
        // 2. 提交Runnable任務(wù)(無(wú)返回值)
        fixedPool.submit(() -> {
            for (int i = 0; i < 3; i++) {
                System.out.println(Thread.currentThread().getName() + ": Runnable任務(wù)");
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        // 3. 提交Callable任務(wù)(有返回值,返回Future對(duì)象)
        Future<Integer> future = fixedPool.submit(() -> {
            int sum = 0;
            for (int i = 1; i <= 5; i++) {
                sum += i;
                Thread.sleep(100);
            }
            return sum;
        });
        // 4. 獲取Callable任務(wù)結(jié)果
        System.out.println("Callable任務(wù)結(jié)果:" + future.get()); // 輸出:15
        // 5. 關(guān)閉線程池(重要!否則JVM不會(huì)退出)
        // shutdown():不再接受新任務(wù),等待已提交任務(wù)執(zhí)行完畢后關(guān)閉
        fixedPool.shutdown();
        // shutdownNow():立即關(guān)閉,嘗試中斷正在執(zhí)行的任務(wù)(可能導(dǎo)致數(shù)據(jù)不一致)
        // fixedPool.shutdownNow();
    }
}

線程池的核心優(yōu)勢(shì)

  • 復(fù)用線程:避免頻繁創(chuàng)建 / 銷毀線程的開(kāi)銷(線程創(chuàng)建需分配??臻g、與內(nèi)核線程關(guān)聯(lián),銷毀需釋放資源);
  • 控制并發(fā)數(shù):限制同時(shí)運(yùn)行的線程數(shù)量,避免線程過(guò)多導(dǎo)致 CPU 切換頻繁(上下文切換耗時(shí))和內(nèi)存溢出(每個(gè)線程默認(rèn)棧大小 1MB);
  • 任務(wù)管理:提供任務(wù)隊(duì)列、拒絕策略(如任務(wù)滿時(shí)丟棄、阻塞)、線程空閑超時(shí)銷毀等功能,簡(jiǎn)化任務(wù)管理;
  • 監(jiān)控與擴(kuò)展:可通過(guò)ThreadPoolExecutor自定義線程池(如設(shè)置核心線程數(shù)、最大線程數(shù)、任務(wù)隊(duì)列),支持監(jiān)控線程池狀態(tài)(如活躍線程數(shù)、完成任務(wù)數(shù))。

線程池選擇建議

任務(wù)類型推薦線程池類型核心參數(shù)設(shè)置(參考)
CPU 密集型(計(jì)算)newFixedThreadPool(n)n = CPU 核心數(shù)(Runtime.getRuntime ().availableProcessors ())
IO 密集型(網(wǎng)絡(luò) / 文件)newCachedThreadPool()線程數(shù)隨任務(wù)增長(zhǎng),空閑 60 秒銷毀
定時(shí) / 延遲任務(wù)newScheduledThreadPool(n)n = 2~4(根據(jù)定時(shí)任務(wù)數(shù)量調(diào)整)
順序執(zhí)行任務(wù)newSingleThreadExecutor()僅 1 個(gè)線程,保證任務(wù)按提交順序執(zhí)行

四、多線程的核心挑戰(zhàn):線程安全與同步控制

多線程并發(fā)執(zhí)行時(shí),若多個(gè)線程操作共享資源(如共享變量、文件、數(shù)據(jù)庫(kù)連接),會(huì)出現(xiàn) “線程安全問(wèn)題”(數(shù)據(jù)競(jìng)爭(zhēng)、結(jié)果不一致)。解決線程安全的核心是同步控制—— 確保同一時(shí)間只有一個(gè)線程操作共享資源。

1. 線程安全問(wèn)題的根源:可見(jiàn)性、原子性、有序性

Java 內(nèi)存模型(JMM)定義了線程與主內(nèi)存之間的交互規(guī)則,多線程安全問(wèn)題本質(zhì)是 JMM 的三大特性被破壞:

(1)可見(jiàn)性:一個(gè)線程修改的共享變量,其他線程無(wú)法立即看到
  • 原因:CPU 緩存(線程讀取變量時(shí)先加載到 CPU 緩存,修改后未及時(shí)刷回主內(nèi)存,其他線程讀取的仍是緩存中的舊值);
  • 示例:
class VisibilityDemo {
    private boolean stopFlag = false; // 共享變量(無(wú)可見(jiàn)性保證)
    public void start() {
        new Thread(() -> {
            while (!stopFlag) { // 線程1讀取的是CPU緩存中的false,即使主線程修改也無(wú)法看到
                // 循環(huán)執(zhí)行
            }
            System.out.println("線程停止");
        }).start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        stopFlag = true; // 主線程修改stopFlag為true,但線程1無(wú)法看到
        System.out.println("主線程已設(shè)置stopFlag為true");
    }
    public static void main(String[] args) {
        new VisibilityDemo().start(); // 線程1不會(huì)停止,陷入無(wú)限循環(huán)
    }
}
(2)原子性:一個(gè)操作不可分割,要么全部執(zhí)行,要么全部不執(zhí)行
  • 原因:多線程下操作被 CPU 指令拆分(如count++拆分為 “讀取 count→加 1→寫(xiě)入 count” 三步),線程切換導(dǎo)致操作交錯(cuò);
  • 示例:
class AtomicityDemo {
    private int count = 0; // 共享變量(無(wú)原子性保證)
    // 非線程安全的方法
    public void increment() {
        count++; // 原子性問(wèn)題:多線程下操作交錯(cuò),導(dǎo)致計(jì)數(shù)不準(zhǔn)確
    }
    public int getCount() {
        return count;
    }
    public static void main(String[] args) throws InterruptedException {
        AtomicityDemo demo = new AtomicityDemo();
        int threadNum = 2;
        Thread[] threads = new Thread[threadNum];
        // 2個(gè)線程各執(zhí)行1000次increment()
        for (int i = 0; i < threadNum; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    demo.increment();
                }
            });
            threads[i].start();
        }
        // 等待所有線程執(zhí)行完畢
        for (Thread thread : threads) {
            thread.join();
        }
        // 預(yù)期結(jié)果2000,實(shí)際結(jié)果可能小于2000(如1850)
        System.out.println("最終計(jì)數(shù):" + demo.getCount());
    }
}
(3)有序性:程序執(zhí)行順序與代碼順序不一致
  • 原因:CPU 指令重排序(為提升性能,CPU 會(huì)在不影響單線程結(jié)果的前提下,調(diào)整指令執(zhí)行順序);
  • 示例:
class OrderingDemo {
    private int a = 0;
    private boolean flag = false;
    // 線程1執(zhí)行
    public void writer() {
        a = 1; // 指令1
        flag = true; // 指令2(可能被重排序到指令1之前)
    }
    // 線程2執(zhí)行
    public void reader() {
        if (flag) { // 若flag=true,線程2認(rèn)為a已被賦值為1
            System.out.println(a); // 可能輸出0(指令2先執(zhí)行,指令1未執(zhí)行)
        }
    }
}

2. 解決線程安全:3 種核心同步機(jī)制

(1)synchronized 關(guān)鍵字(內(nèi)置鎖,JVM 層面)

synchronized是 Java 內(nèi)置的同步機(jī)制,通過(guò) “對(duì)象監(jiān)視器(Object Monitor)” 實(shí)現(xiàn),保證 “同一時(shí)間只有一個(gè)線程執(zhí)行同步代碼塊 / 方法”,可同時(shí)解決可見(jiàn)性、原子性、有序性問(wèn)題。

使用方式

同步方法:鎖對(duì)象是this(非靜態(tài)方法)或 “類對(duì)象”(靜態(tài)方法);

// 非靜態(tài)同步方法(鎖是this)
public synchronized void safeIncrement() {
    count++; // 原子操作
}
// 靜態(tài)同步方法(鎖是AtomicityDemo.class)
public static synchronized void staticSafeIncrement() {
    staticCount++;
}

同步代碼塊:鎖對(duì)象可自定義(推薦,縮小同步范圍,提升性能);

public void safeIncrement() {
    synchronized (this) { // 鎖對(duì)象為this(可替換為其他對(duì)象,如private final Object lock = new Object())
        count++;
    }
}

原理

  • 進(jìn)入同步塊時(shí),線程需獲取鎖(若鎖被占用,線程進(jìn)入BLOCKED狀態(tài),加入鎖的等待隊(duì)列);
  • 釋放鎖時(shí),線程釋放鎖,喚醒等待隊(duì)列中的一個(gè)線程(非公平喚醒,即不按等待順序);
  • JMM 保證:鎖釋放前,線程修改的共享變量會(huì)刷回主內(nèi)存(可見(jiàn)性);鎖獲取后,線程會(huì)從主內(nèi)存重新加載共享變量(避免緩存舊值);同時(shí)禁止指令重排序(有序性)。

優(yōu)缺點(diǎn)

  • 優(yōu)點(diǎn):使用簡(jiǎn)單,JVM 自動(dòng)管理鎖的獲取與釋放(無(wú)需手動(dòng)釋放,避免死鎖),兼容性好;
  • 缺點(diǎn):鎖粒度大(默認(rèn)鎖定整個(gè)方法或代碼塊),不支持中斷、超時(shí)獲取、公平鎖,并發(fā)性能較低(JDK 1.6 后優(yōu)化,引入偏向鎖、輕量級(jí)鎖、重量級(jí)鎖,性能大幅提升)。
(2)Lock 接口(顯式鎖,JDK 層面)

java.util.concurrent.locks.Lock接口是synchronized的增強(qiáng)版,支持更靈活的鎖操作(可中斷、超時(shí)、公平鎖),核心實(shí)現(xiàn)類是ReentrantLock(可重入鎖)。

使用方式

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class LockDemo {
    private int count = 0;
    // 創(chuàng)建Lock對(duì)象(默認(rèn)非公平鎖,傳入true創(chuàng)建公平鎖)
    private final Lock lock = new ReentrantLock(true);
    public void safeIncrement() {
        lock.lock(); // 加鎖(若鎖被占用,線程進(jìn)入WAITING狀態(tài),加入條件隊(duì)列)
        try {
            count++; // 臨界區(qū)代碼(原子操作)
        } finally {
            lock.unlock(); // 釋放鎖(必須在finally中,避免異常導(dǎo)致鎖未釋放)
        }
    }
    // 支持中斷的加鎖(避免線程無(wú)限等待)
    public void safeIncrementWithInterrupt() throws InterruptedException {
        if (lock.tryLock(1000, java.util.concurrent.TimeUnit.MILLISECONDS)) { // 超時(shí)1秒
            try {
                count++;
            } finally {
                lock.unlock();
            }
        } else {
            System.out.println("獲取鎖超時(shí)");
        }
    }
    public int getCount() {
        return count;
    }
}

核心特性

  • 可中斷:通過(guò)lock.lockInterruptibly()tryLock(long, TimeUnit),線程在等待鎖時(shí)可被中斷(Thread.interrupt());
  • 超時(shí)獲?。?code>tryLock(long, TimeUnit)允許線程在指定時(shí)間內(nèi)獲取鎖,超時(shí)后返回false,避免無(wú)限等待;
  • 公平鎖:構(gòu)造ReentrantLock(true)創(chuàng)建公平鎖,鎖釋放時(shí)優(yōu)先喚醒等待時(shí)間最長(zhǎng)的線程(避免線程饑餓),但公平鎖性能較低(需維護(hù)等待隊(duì)列順序);
  • 條件變量:通過(guò)lock.newCondition()創(chuàng)建Condition對(duì)象,支持精細(xì)化線程通信(如 “生產(chǎn)者 - 消費(fèi)者” 模型中,區(qū)分 “空隊(duì)列等待” 和 “滿隊(duì)列等待”)。

Lock vs synchronized

特性synchronizedReentrantLock
鎖獲取 / 釋放隱式(JVM 自動(dòng)管理)顯式(手動(dòng) lock ()/unlock ())
可中斷性不可中斷(BLOCKED 狀態(tài)無(wú)法中斷)可中斷(lockInterruptibly ()/tryLock ())
超時(shí)獲取不支持支持(tryLock (long))
公平鎖不支持(非公平)支持(構(gòu)造器傳入 true)
條件變量支持(wait ()/notify (),1 個(gè)條件隊(duì)列)支持(Condition,多個(gè)條件隊(duì)列)
鎖狀態(tài)查詢不支持支持(isLocked ()/hasQueuedThreads ())
(3)原子類(AtomicXXX,無(wú)鎖機(jī)制)

java.util.concurrent.atomic包下的原子類,基于CAS(Compare and Swap,比較并交換) 實(shí)現(xiàn)無(wú)鎖同步,無(wú)需加鎖即可保證原子性,性能優(yōu)于synchronizedLock(無(wú)鎖競(jìng)爭(zhēng)開(kāi)銷)。

核心原子類

  • 基本類型:AtomicIntegerAtomicLong、AtomicBoolean;
  • 引用類型:AtomicReferenceAtomicStampedReference(解決 ABA 問(wèn)題);
  • 數(shù)組類型:AtomicIntegerArray、AtomicLongArray
  • 字段更新器:AtomicIntegerFieldUpdater(原子更新對(duì)象的私有字段)。

使用方式

import java.util.concurrent.atomic.AtomicInteger;
class AtomicDemo {
    // 原子類:保證count++是原子操作
    private final AtomicInteger count = new AtomicInteger(0);
    public void safeIncrement() {
        count.incrementAndGet(); // 原子性的count++(等價(jià)于count = count + 1)
    }
    // 其他原子操作
    public void add() {
        count.addAndGet(5); // 原子性的count += 5
    }
    public int getCount() {
        return count.get();
    }
    public static void main(String[] args) throws InterruptedException {
        AtomicDemo demo = new AtomicDemo();
        Thread[] threads = new Thread[2];
        for (int i = 0; i < 2; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    demo.safeIncrement();
                }
            });
            threads[i].start();
        }
        for (Thread thread : threads) {
            thread.join();
        }
        System.out.println("最終計(jì)數(shù):" + demo.getCount()); // 輸出:2000(準(zhǔn)確)
    }
}

CAS 原理:CAS 是一種無(wú)鎖算法,包含 3 個(gè)參數(shù):

  • V:共享變量在主內(nèi)存中的地址;
  • A:線程預(yù)期的變量舊值;
  • B:變量的新值。

執(zhí)行邏輯:僅當(dāng)主內(nèi)存中 V 的值等于 A 時(shí),才將 V 的值更新為 B,否則不操作;整個(gè)過(guò)程由 CPU 指令cmpxchg保證原子性(無(wú)需加鎖)。

優(yōu)缺點(diǎn)

  • 優(yōu)點(diǎn):無(wú)鎖競(jìng)爭(zhēng),性能高(尤其在低并發(fā)場(chǎng)景),無(wú)需擔(dān)心死鎖;
  • 缺點(diǎn):僅支持單個(gè)變量的原子操作(無(wú)法實(shí)現(xiàn)多個(gè)變量的原子組合操作),存在 “ABA 問(wèn)題”(變量從 A→B→A,CAS 誤判為未修改,可通過(guò)AtomicStampedReference的版本號(hào)解決)。

3. 線程通信:多線程協(xié)作的核心機(jī)制

線程通信是指多個(gè)線程通過(guò)共享變量或特定 API 協(xié)調(diào)執(zhí)行順序(如 “線程 A 執(zhí)行完任務(wù) 1 后,線程 B 再執(zhí)行任務(wù) 2”),Java 提供 3 種核心通信方式:

(1)wait ()/notify ()/notifyAll ()(基于 synchronized)

這三個(gè)方法是Object類的成員方法,必須在synchronized同步塊 / 方法中使用,依賴 “對(duì)象監(jiān)視器” 的等待隊(duì)列和同步隊(duì)列實(shí)現(xiàn)通信。

  • wait():線程釋放當(dāng)前對(duì)象的鎖,進(jìn)入該對(duì)象的等待隊(duì)列,狀態(tài)轉(zhuǎn)為WAITING,等待被notify()喚醒;
  • notify():?jiǎn)拘言搶?duì)象等待隊(duì)列中的一個(gè)隨機(jī)線程,使其進(jìn)入同步隊(duì)列,競(jìng)爭(zhēng)鎖(狀態(tài)轉(zhuǎn)為BLOCKED);
  • notifyAll():?jiǎn)拘言搶?duì)象等待隊(duì)列中的所有線程,使其進(jìn)入同步隊(duì)列競(jìng)爭(zhēng)鎖。

示例:生產(chǎn)者 - 消費(fèi)者模型(單生產(chǎn)者單消費(fèi)者)

// 共享緩沖區(qū)(存儲(chǔ)產(chǎn)品)
class Buffer {
    private int[] products = new int[5]; // 緩沖區(qū)大小為5
    private int count = 0; // 當(dāng)前產(chǎn)品數(shù)量
    private int putIndex = 0; // 生產(chǎn)者放入產(chǎn)品的索引
    private int takeIndex = 0; // 消費(fèi)者取出產(chǎn)品的索引
    // 生產(chǎn)者放入產(chǎn)品
    public synchronized void put(int product) throws InterruptedException {
        // 緩沖區(qū)滿,等待消費(fèi)者取出(用while避免虛假喚醒)
        while (count == products.length) {
            System.out.println("緩沖區(qū)滿,生產(chǎn)者等待");
            wait(); // 釋放鎖,進(jìn)入等待隊(duì)列
        }
        // 放入產(chǎn)品
        products[putIndex] = product;
        putIndex = (putIndex + 1) % products.length;
        count++;
        System.out.println("生產(chǎn)者放入產(chǎn)品:" + product + ",當(dāng)前數(shù)量:" + count);
        notify(); // 喚醒消費(fèi)者(緩沖區(qū)有產(chǎn)品了)
    }
    // 消費(fèi)者取出產(chǎn)品
    public synchronized void take() throws InterruptedException {
        // 緩沖區(qū)空,等待生產(chǎn)者放入
        while (count == 0) {
            System.out.println("緩沖區(qū)空,消費(fèi)者等待");
            wait(); // 釋放鎖,進(jìn)入等待隊(duì)列
        }
        // 取出產(chǎn)品
        int product = products[takeIndex];
        takeIndex = (takeIndex + 1) % products.length;
        count--;
        System.out.println("消費(fèi)者取出產(chǎn)品:" + product + ",當(dāng)前數(shù)量:" + count);
        notify(); // 喚醒生產(chǎn)者(緩沖區(qū)有空位了)
    }
}
// 生產(chǎn)者線程
class Producer extends Thread {
    private Buffer buffer;
    public Producer(Buffer buffer) {
        this.buffer = buffer;
    }
    @Override
    public void run() {
        for (int i = 1; i <= 10; i++) {
            try {
                buffer.put(i);
                Thread.sleep(500); // 模擬生產(chǎn)耗時(shí)
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
// 消費(fèi)者線程
class Consumer extends Thread {
    private Buffer buffer;
    public Consumer(Buffer buffer) {
        this.buffer = buffer;
    }
    @Override
    public void run() {
        for (int i = 1; i <= 10; i++) {
            try {
                buffer.take();
                Thread.sleep(800); // 模擬消費(fèi)耗時(shí)
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
// 測(cè)試
public class WaitNotifyDemo {
    public static void main(String[] args) {
        Buffer buffer = new Buffer();
        new Producer(buffer).start();
        new Consumer(buffer).start();
    }
}

輸出結(jié)果(片段)

生產(chǎn)者放入產(chǎn)品:1,當(dāng)前數(shù)量:1
消費(fèi)者取出產(chǎn)品:1,當(dāng)前數(shù)量:0
緩沖區(qū)空,消費(fèi)者等待
生產(chǎn)者放入產(chǎn)品:2,當(dāng)前數(shù)量:1
消費(fèi)者取出產(chǎn)品:2,當(dāng)前數(shù)量:0
緩沖區(qū)空,消費(fèi)者等待
生產(chǎn)者放入產(chǎn)品:3,當(dāng)前數(shù)量:1
生產(chǎn)者放入產(chǎn)品:4,當(dāng)前數(shù)量:2
消費(fèi)者取出產(chǎn)品:3,當(dāng)前數(shù)量:1
...

關(guān)鍵注意點(diǎn):用while循環(huán)判斷條件(而非if),避免 “虛假喚醒”—— 線程可能在未被notify()的情況下被喚醒(如操作系統(tǒng)信號(hào)中斷),while循環(huán)會(huì)重新檢查條件,確保邏輯正確。

(2)Condition 接口(基于 Lock)

ConditionLock的配套通信工具,通過(guò)Lock.newCondition()創(chuàng)建,功能與wait()/notify()類似,但支持更精細(xì)的線程分組通信(一個(gè)Lock可創(chuàng)建多個(gè)Condition,實(shí)現(xiàn)不同線程組的獨(dú)立喚醒)。

示例:用 Condition 實(shí)現(xiàn)生產(chǎn)者 - 消費(fèi)者(多生產(chǎn)者多消費(fèi)者)

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class ConditionBuffer {
    private int[] products = new int[5];
    private int count = 0;
    private int putIndex = 0;
    private int takeIndex = 0;
    private final Lock lock = new ReentrantLock();
    // 創(chuàng)建兩個(gè)Condition:生產(chǎn)者等待隊(duì)列和消費(fèi)者等待隊(duì)列
    private final Condition producerCond = lock.newCondition();
    private final Condition consumerCond = lock.newCondition();
    public void put(int product) throws InterruptedException {
        lock.lock();
        try {
            while (count == products.length) {
                System.out.println("緩沖區(qū)滿,生產(chǎn)者等待");
                producerCond.await(); // 生產(chǎn)者進(jìn)入生產(chǎn)者等待隊(duì)列
            }
            products[putIndex] = product;
            putIndex = (putIndex + 1) % products.length;
            count++;
            System.out.println("生產(chǎn)者放入產(chǎn)品:" + product + ",當(dāng)前數(shù)量:" + count);
            consumerCond.signal(); // 僅喚醒消費(fèi)者等待隊(duì)列的線程(精準(zhǔn)喚醒)
        } finally {
            lock.unlock();
        }
    }
    public int take() throws InterruptedException {
        lock.lock();
        try {
            while (count == 0) {
                System.out.println("緩沖區(qū)空,消費(fèi)者等待");
                consumerCond.await(); // 消費(fèi)者進(jìn)入消費(fèi)者等待隊(duì)列
            }
            int product = products[takeIndex];
            takeIndex = (takeIndex + 1) % products.length;
            count--;
            System.out.println("消費(fèi)者取出產(chǎn)品:" + product + ",當(dāng)前數(shù)量:" + count);
            producerCond.signal(); // 僅喚醒生產(chǎn)者等待隊(duì)列的線程
            return product;
        } finally {
            lock.unlock();
        }
    }
}

優(yōu)勢(shì)synchronizedwait()/notify()只能喚醒 “所有線程” 或 “一個(gè)隨機(jī)線程”,而Condition可通過(guò)多個(gè)條件隊(duì)列實(shí)現(xiàn) “精準(zhǔn)喚醒”(如只喚醒生產(chǎn)者或只喚醒消費(fèi)者),減少不必要的鎖競(jìng)爭(zhēng),提升并發(fā)性能。

(3)JUC 工具類(CountDownLatch/CyclicBarrier/Semaphore)

java.util.concurrent包提供了更高級(jí)的線程通信工具類,簡(jiǎn)化復(fù)雜的協(xié)作場(chǎng)景:

  • CountDownLatch(倒計(jì)時(shí)門閂):讓一個(gè)線程等待其他多個(gè)線程執(zhí)行完畢后再繼續(xù)(如主線程等待 10 個(gè)任務(wù)線程執(zhí)行完);
import java.util.concurrent.CountDownLatch;
public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
        int taskNum = 3;
        CountDownLatch latch = new CountDownLatch(taskNum);
        for (int i = 0; i < taskNum; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "執(zhí)行任務(wù)");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    latch.countDown(); // 任務(wù)完成,倒計(jì)時(shí)減1
                }
            }, "任務(wù)線程" + i).start();
        }
        latch.await(); // 主線程等待,直到倒計(jì)時(shí)為0
        System.out.println("所有任務(wù)執(zhí)行完畢,主線程繼續(xù)");
    }
}
  • CyclicBarrier(循環(huán)屏障):讓多個(gè)線程在某個(gè)屏障點(diǎn)等待,直到所有線程都到達(dá)后再一起繼續(xù)(如 3 個(gè)線程都到達(dá)屏障后,再同時(shí)執(zhí)行后續(xù)邏輯);
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierDemo {
    public static void main(String[] args) {
        int threadNum = 3;
        // 屏障動(dòng)作:所有線程到達(dá)后執(zhí)行
        CyclicBarrier barrier = new CyclicBarrier(threadNum, () -> {
            System.out.println("所有線程到達(dá)屏障,執(zhí)行屏障動(dòng)作");
        });
        for (int i = 0; i < threadNum; i++) {
            new Thread(() -> {
                try {
                    System.out.println(Thread.currentThread().getName() + "正在執(zhí)行");
                    Thread.sleep((long) (Math.random() * 1000));
                    System.out.println(Thread.currentThread().getName() + "到達(dá)屏障");
                    barrier.await(); // 等待其他線程到達(dá)
                    System.out.println(Thread.currentThread().getName() + "繼續(xù)執(zhí)行");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }, "線程" + i).start();
        }
    }
}
  • Semaphore(信號(hào)量):控制同時(shí)訪問(wèn)某個(gè)資源的線程數(shù)量(如限制最多 5 個(gè)線程同時(shí)訪問(wèn)數(shù)據(jù)庫(kù)連接);
import java.util.concurrent.Semaphore;
public class SemaphoreDemo {
    public static void main(String[] args) {
        int permitNum = 2; // 允許同時(shí)訪問(wèn)的線程數(shù)
        Semaphore semaphore = new Semaphore(permitNum);
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                try {
                    semaphore.acquire(); // 獲取許可(若無(wú)許可,線程阻塞)
                    System.out.println(Thread.currentThread().getName() + "獲取許可,訪問(wèn)資源");
                    Thread.sleep(1000); // 模擬訪問(wèn)資源耗時(shí)
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    semaphore.release(); // 釋放許可
                    System.out.println(Thread.currentThread().getName() + "釋放許可");
                }
            }, "線程" + i).start();
        }
    }
}

五、多線程的實(shí)戰(zhàn)最佳實(shí)踐:避坑與優(yōu)化

1. 優(yōu)先使用線程池,避免手動(dòng)創(chuàng)建線程

  • 手動(dòng)創(chuàng)建線程的問(wèn)題:頻繁創(chuàng)建 / 銷毀線程開(kāi)銷大(線程創(chuàng)建需分配??臻g、與內(nèi)核線程關(guān)聯(lián),銷毀需釋放資源),線程過(guò)多導(dǎo)致 CPU 切換頻繁(上下文切換耗時(shí)約 1~10 微秒),內(nèi)存占用高(每個(gè)線程默認(rèn)棧大小 1MB);
  • 線程池優(yōu)化建議
    • 避免使用Executors的默認(rèn)線程池(如newCachedThreadPool可能創(chuàng)建無(wú)限線程,導(dǎo)致 OOM),推薦用ThreadPoolExecutor自定義線程池:
      // 自定義線程池(推薦)
      ThreadPoolExecutor executor = new ThreadPoolExecutor(
          2, // 核心線程數(shù)(始終存活,即使空閑)
          4, // 最大線程數(shù)(核心線程滿且隊(duì)列滿時(shí),最多創(chuàng)建的線程數(shù))
          60, // 空閑線程存活時(shí)間(超過(guò)核心線程數(shù)的線程,空閑60秒后銷毀)
          TimeUnit.SECONDS,
          new ArrayBlockingQueue<>(10), // 任務(wù)隊(duì)列(核心線程滿時(shí),任務(wù)放入隊(duì)列)
          Executors.defaultThreadFactory(), // 線程工廠(創(chuàng)建線程)
          new ThreadPoolExecutor.AbortPolicy() // 拒絕策略(任務(wù)滿時(shí)拋出異常)
      );
    • 根據(jù)任務(wù)類型設(shè)置線程數(shù):CPU 密集型任務(wù)(核心線程數(shù) = CPU 核心數(shù)),IO 密集型任務(wù)(核心線程數(shù) = CPU 核心數(shù) ×2);
    • 顯式關(guān)閉線程池:在應(yīng)用關(guān)閉時(shí)調(diào)用executor.shutdown(),避免線程泄漏(尤其在 Web 應(yīng)用中,容器銷毀時(shí)線程仍在運(yùn)行)。

2. 避免線程安全問(wèn)題的核心原則

  • 最小化共享資源:盡量減少線程間共享的變量,優(yōu)先使用局部變量(線程私有,無(wú)安全問(wèn)題);
  • 使用線程安全的數(shù)據(jù)結(jié)構(gòu):如ConcurrentHashMap(線程安全的 Map)、CopyOnWriteArrayList(線程安全的 List,讀多寫(xiě)少場(chǎng)景)、BlockingQueue(線程安全的隊(duì)列,用于生產(chǎn)者 - 消費(fèi)者);
  • 避免鎖嵌套:鎖嵌套(如線程 A 持有鎖 1 等待鎖 2,線程 B 持有鎖 2 等待鎖 1)會(huì)導(dǎo)致死鎖,應(yīng)按固定順序獲取鎖(如始終先獲取鎖 1,再獲取鎖 2);
  • 使用無(wú)鎖機(jī)制:簡(jiǎn)單變量操作優(yōu)先用原子類(AtomicXXX),避免加鎖;復(fù)雜場(chǎng)景用ThreadLocal存儲(chǔ)線程私有變量(如用戶會(huì)話信息),避免共享。

3. 優(yōu)化鎖性能的關(guān)鍵技巧

  • 縮小同步范圍:用synchronized代碼塊替代同步方法,僅同步 “臨界區(qū)”(如只同步count++,而非整個(gè)方法);
// 優(yōu)化前:同步整個(gè)方法
public synchronized void process() {
    // 非臨界區(qū)代碼(無(wú)需同步)
    log();
    // 臨界區(qū)代碼(需同步)
    count++;
}
// 優(yōu)化后:僅同步臨界區(qū)
public void process() {
    // 非臨界區(qū)代碼(無(wú)需同步)
    log();
    synchronized (this) {
        // 臨界區(qū)代碼(需同步)
        count++;
    }
}
  • 使用讀寫(xiě)鎖(ReentrantReadWriteLock):讀多寫(xiě)少場(chǎng)景用讀寫(xiě)鎖,讀鎖可共享(多個(gè)線程同時(shí)讀),寫(xiě)鎖排他(僅一個(gè)線程寫(xiě)),提升讀操作并發(fā)度;
import java.util.concurrent.locks.ReentrantReadWriteLock;
class ReadWriteLockDemo {
    private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
    private final ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock();
    private final ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();
    private int data;
    // 讀操作(共享)
    public int readData() {
        readLock.lock();
        try {
            return data;
        } finally {
            readLock.unlock();
        }
    }
    // 寫(xiě)操作(排他)
    public void writeData(int data) {
        writeLock.lock();
        try {
            this.data = data;
        } finally {
            writeLock.unlock();
        }
    }
}
  • 避免鎖競(jìng)爭(zhēng):將大鎖拆分為小鎖(如ConcurrentHashMap的分段鎖,JDK 1.8 后改為 CAS+synchronized),減少鎖競(jìng)爭(zhēng);
  • 使用樂(lè)觀鎖:非核心場(chǎng)景用 CAS 替代synchronized(如用AtomicInteger替代synchronized的計(jì)數(shù)),避免鎖開(kāi)銷。

4. 正確處理線程中斷

線程中斷是線程間的 “協(xié)作信號(hào)”,表示 “希望你盡快終止”,而非強(qiáng)制終止線程,正確處理方式:

  • 在循環(huán)中檢查中斷狀態(tài):通過(guò)Thread.currentThread().isInterrupted()判斷是否被中斷,若中斷則退出;
public void run() {
    while (!Thread.currentThread().isInterrupted()) {
        // 執(zhí)行任務(wù)邏輯
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            // 恢復(fù)中斷狀態(tài)(否則后續(xù)循環(huán)無(wú)法感知中斷)
            Thread.currentThread().interrupt();
            break; // 退出循環(huán),終止線程
        }
    }
}
  • 不忽略InterruptedExceptionsleep()wait()、join()等方法會(huì)拋出InterruptedException,捕獲后應(yīng)恢復(fù)中斷狀態(tài)或終止線程,避免上層調(diào)用者無(wú)法感知中斷;
  • 避免使用stop()stop()已廢棄,強(qiáng)制終止線程會(huì)導(dǎo)致資源泄漏(如未關(guān)閉文件流、數(shù)據(jù)庫(kù)連接)。

5. 避免 ThreadLocal 泄漏

ThreadLocal用于存儲(chǔ)線程私有變量(每個(gè)線程有獨(dú)立副本),但使用不當(dāng)會(huì)導(dǎo)致內(nèi)存泄漏:

  • 泄漏原因Thread持有ThreadLocalMap,ThreadLocalMapEntry是弱引用(WeakReference),若ThreadLocal對(duì)象被回收,Entry的 key 變?yōu)?code>null,但 value 仍被Thread持有,若Thread長(zhǎng)期運(yùn)行(如線程池線程),value 無(wú)法回收;
  • 解決方式:使用完ThreadLocal后,調(diào)用remove()方法清除 value(尤其在線程池場(chǎng)景中);
public class ThreadLocalDemo {
    private static final ThreadLocal<String> threadLocal = new ThreadLocal<>();
    public void process() {
        try {
            threadLocal.set("線程私有數(shù)據(jù)");
            // 使用數(shù)據(jù)...
        } finally {
            threadLocal.remove(); // 清除value,避免泄漏
        }
    }
}

六、常見(jiàn)面試題與誤區(qū)解析

1. 高頻面試題

  • Q:start () 和 run () 的區(qū)別?A:start()是 “啟動(dòng)線程”,調(diào)用后 JVM 創(chuàng)建內(nèi)核線程,線程狀態(tài)從 NEW→RUNNABLE,run()方法在新線程中執(zhí)行;run()是普通方法,直接調(diào)用僅在當(dāng)前線程中執(zhí)行,不會(huì)創(chuàng)建新線程。
  • Q:synchronized 和 Lock 的區(qū)別?A:synchronized是 JVM 內(nèi)置鎖,隱式獲取 / 釋放,不支持中斷、超時(shí)、公平鎖;Lock是 JDK 顯式鎖,手動(dòng)lock()/unlock(),支持中斷、超時(shí)、公平鎖、多條件變量,靈活性更高。
  • Q:什么是死鎖?如何避免死鎖?A:死鎖是指多個(gè)線程互相持有對(duì)方需要的鎖,無(wú)限等待(如線程 A 持有鎖 1 等待鎖 2,線程 B 持有鎖 2 等待鎖 1)。避免方式:按固定順序獲取鎖、設(shè)置鎖超時(shí)時(shí)間(tryLock())、使用LocklockInterruptibly()支持中斷、避免鎖嵌套。
  • Q:ThreadLocal 的原理?為什么會(huì)泄漏?A:ThreadLocal通過(guò)ThreadThreadLocalMap存儲(chǔ)線程私有變量,每個(gè)線程有獨(dú)立的ThreadLocalMap。泄漏原因:ThreadLocalMapEntrykey 是弱引用,ThreadLocal回收后 key 為null,但 value 被Thread持有,若Thread長(zhǎng)期運(yùn)行,value 無(wú)法回收;解決方式是使用后調(diào)用remove()。
  • Q:volatile 關(guān)鍵字的作用?能保證原子性嗎?A:volatile的作用是保證共享變量的可見(jiàn)性(一個(gè)線程修改后,其他線程立即看到)和有序性(禁止指令重排序),但不能保證原子性(如volatile int count; count++仍非原子操作,需用原子類或加鎖)。

2. 典型誤區(qū)

  • 誤區(qū) 1:多線程一定比單線程快錯(cuò)誤。CPU 密集型任務(wù)在單核 CPU 上,多線程因上下文切換開(kāi)銷,可能比單線程慢;IO 密集型任務(wù)多線程更有優(yōu)勢(shì)。
  • 誤區(qū) 2:線程池的線程數(shù)越多越好錯(cuò)誤。線程數(shù)過(guò)多會(huì)導(dǎo)致 CPU 上下文切換頻繁(每次切換耗時(shí) 1~10 微秒),內(nèi)存占用高(每個(gè)線程默認(rèn)棧大小 1MB),反而降低性能;應(yīng)根據(jù)任務(wù)類型(CPU 密集型 / IO 密集型)合理設(shè)置線程數(shù)。
  • 誤區(qū) 3:synchronized 是重量級(jí)鎖,性能差錯(cuò)誤。JDK 1.6 后對(duì)synchronized進(jìn)行了優(yōu)化,引入偏向鎖、輕量級(jí)鎖、重量級(jí)鎖三級(jí)鎖機(jī)制:無(wú)競(jìng)爭(zhēng)時(shí)用偏向鎖(僅標(biāo)記線程 ID),低競(jìng)爭(zhēng)時(shí)用輕量級(jí)鎖(CAS 自旋),高競(jìng)爭(zhēng)時(shí)用重量級(jí)鎖(內(nèi)核態(tài)阻塞),性能接近Lock。
  • 誤區(qū) 4:原子類是無(wú)鎖的,所以沒(méi)有性能開(kāi)銷錯(cuò)誤。原子類基于 CAS 實(shí)現(xiàn),CAS 是自旋操作(未成功時(shí)循環(huán)重試),高并發(fā)場(chǎng)景下自旋次數(shù)多,會(huì)占用 CPU 資源,性能可能低于synchronized(重量級(jí)鎖阻塞時(shí)不占用 CPU)。

總結(jié)

Java 多線程是并發(fā)編程的基石,理解線程的生命周期、同步與通信機(jī)制,是寫(xiě)出高效、安全的并發(fā)代碼的前提。從簡(jiǎn)單的Thread創(chuàng)建到復(fù)雜的線程池管理,從synchronized的基礎(chǔ)同步到CAS的無(wú)鎖優(yōu)化,每一種機(jī)制都對(duì)應(yīng)特定的問(wèn)題場(chǎng)景。

在實(shí)際開(kāi)發(fā)中,無(wú)需過(guò)度追求 “復(fù)雜的并發(fā)技巧”,而應(yīng)遵循 “簡(jiǎn)單優(yōu)先” 原則:能用單線程解決的問(wèn)題,不濫用多線程;能通過(guò)原子類或線程池解決的問(wèn)題,不手動(dòng)實(shí)現(xiàn)復(fù)雜的鎖邏輯。只有在理解底層原理的基礎(chǔ)上,才能靈活應(yīng)對(duì)各種并發(fā)場(chǎng)景,讓多線程成為提升程序性能的工具,而非引發(fā)問(wèn)題的隱患。

多線程的核心價(jià)值,在于讓程序 “更高效地利用資源”—— 無(wú)論是多核 CPU 的算力,還是 IO 等待時(shí)的 CPU 空閑時(shí)間。掌握多線程,不僅是掌握一種技術(shù),更是理解 “如何在動(dòng)態(tài)變化的環(huán)境中協(xié)調(diào)多個(gè)任務(wù)”,這正是優(yōu)秀工程師的核心能力之一。

到此這篇關(guān)于Java多線程核心機(jī)制全解析的文章就介紹到這了,更多相關(guān)java多線程機(jī)制內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java設(shè)計(jì)模式之代理模式

    Java設(shè)計(jì)模式之代理模式

    這篇文章介紹了Java設(shè)計(jì)模式之代理模式,文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-09-09
  • 淺談Java基于Consul創(chuàng)建分布式鎖

    淺談Java基于Consul創(chuàng)建分布式鎖

    這篇文章主要介紹了淺談基于Consul創(chuàng)建分布式鎖,Consul是HashiCorp公司推出的開(kāi)源工具,用于實(shí)現(xiàn)分布式系統(tǒng)的服務(wù)發(fā)現(xiàn)與配置Consul是分布式的、高可用的、可橫向擴(kuò)展的,需要的朋友可以參考下
    2023-07-07
  • SpringBoot整合RedisTemplate實(shí)現(xiàn)緩存信息監(jiān)控的步驟

    SpringBoot整合RedisTemplate實(shí)現(xiàn)緩存信息監(jiān)控的步驟

    這篇文章主要介紹了SpringBoot整合RedisTemplate實(shí)現(xiàn)緩存信息監(jiān)控,一步一步的實(shí)現(xiàn)?Springboot?整合?Redis?來(lái)存儲(chǔ)數(shù)據(jù),讀取數(shù)據(jù),需要的朋友可以參考下
    2022-01-01
  • java多線程編程實(shí)例

    java多線程編程實(shí)例

    這篇文章主要介紹了java多線程編程實(shí)例,分享了幾則多線程的實(shí)例代碼,具有一定參考價(jià)值,加深多線程編程的理解還是很有幫助的,需要的朋友可以參考下。
    2017-11-11
  • 深入了解Java.Util.Date詳情

    深入了解Java.Util.Date詳情

    這篇文章主要介紹了Java.Util.Date,很少有類能像java.util.Date那樣在堆棧溢出方面引起如此多的類似問(wèn)題,關(guān)于具體原因下文內(nèi)容詳細(xì)介紹,需要的朋友可以參考一下
    2022-06-06
  • 東八區(qū)springboot如何配置序列化

    東八區(qū)springboot如何配置序列化

    本文主要介紹了東八區(qū)springboot如何配置序列化,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-04-04
  • idea離線使用jrebel的超詳細(xì)教程

    idea離線使用jrebel的超詳細(xì)教程

    IDEA上原生是不支持熱部署的,一般更新了 Java 文件后要手動(dòng)重啟 Tomcat 服務(wù)器,才能生效,下面通過(guò)本文給大家分享idea離線使用jrebel的超詳細(xì)教程(親測(cè)有效),感興趣的朋友一起看看吧
    2020-12-12
  • SpringBoot前后端交互、全局異常處理之后端異常信息拋到前端顯示彈窗

    SpringBoot前后端交互、全局異常處理之后端異常信息拋到前端顯示彈窗

    Spring Boot是一個(gè)用于構(gòu)建獨(dú)立的、基于生產(chǎn)級(jí)別的Spring應(yīng)用程序的框架,下面這篇文章主要給大家介紹了關(guān)于SpringBoot前后端交互、全局異常處理之后端異常信息拋到前端顯示彈窗的相關(guān)資料,需要的朋友可以參考下
    2024-08-08
  • java基礎(chǔ)的詳細(xì)了解第五天

    java基礎(chǔ)的詳細(xì)了解第五天

    這篇文章對(duì)Java編程語(yǔ)言的基礎(chǔ)知識(shí)作了一個(gè)較為全面的匯總,在這里給大家分享一下。需要的朋友可以參考,希望能給你帶來(lái)幫助
    2021-08-08
  • java.lang.NullPointerException出現(xiàn)的幾種原因及解決方案

    java.lang.NullPointerException出現(xiàn)的幾種原因及解決方案

    這篇文章主要介紹了java.lang.NullPointerException出現(xiàn)的幾種原因及解決方案,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-05-05

最新評(píng)論

云南省| 和林格尔县| 亚东县| 广丰县| 应城市| 双峰县| 宣威市| 霍城县| 沾化县| 临城县| 类乌齐县| 织金县| 将乐县| 连南| 荥阳市| 鲜城| 五原县| 修武县| 台东县| 翁牛特旗| 酒泉市| 鹤岗市| 界首市| 临朐县| 贵定县| 阆中市| 凤冈县| 阳朔县| 丰都县| 屏东县| 天台县| 阜阳市| 淮滨县| 商洛市| 竹北市| 南投县| 正镶白旗| 松滋市| 定远县| 托里县| 且末县|