自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

【踩坑指南】線程池使用不當(dāng)?shù)奈鍌€(gè)坑

開(kāi)發(fā) 前端
本文給大家介紹了線程池使用不當(dāng)?shù)奈鍌€(gè)坑,分別是線程池中異常消失、線程池決絕策略設(shè)置錯(cuò)誤、重復(fù)創(chuàng)建線程池導(dǎo)致內(nèi)存溢出、使用同一個(gè)線程池執(zhí)行不同類型的任務(wù)、使用 ThreadLocal 和線程池的不兼容問(wèn)題,以及它們的問(wèn)題原因和解決方法。

線程池是 Java 多線程編程中的一個(gè)重要概念,它可以有效地管理和復(fù)用線程資源,提高系統(tǒng)的性能和穩(wěn)定性。但是線程池的使用也有一些注意事項(xiàng)和常見(jiàn)的錯(cuò)誤,如果不小心,就可能會(huì)導(dǎo)致一些嚴(yán)重的問(wèn)題,比如內(nèi)存泄漏、死鎖、性能下降等。最后文末還有免費(fèi)紅包封面可以領(lǐng)取,回饋給各位讀者朋友。

本文將介紹線程池使用不當(dāng)?shù)奈鍌€(gè)坑,以及如何避免和解決它們,大綱如下:

圖片

坑一:線程池中異常消失

線程池執(zhí)行方法時(shí)要添加異常處理,這是一個(gè)老生常談的問(wèn)題,可是直到最近我都有同事還在犯這個(gè)錯(cuò)誤,所以我還是要講一下,不過(guò)我還提到了一種優(yōu)雅的線程池全局異常處理的方法,大家可以往下看。

問(wèn)題原因

@Test
public void test() throws Exception {
    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
        5, 
        10, 
        60,
        TimeUnit.SECONDS, 
        new ArrayBlockingQueue<>(100000));
    Future<Integer> submit = threadPoolExecutor.execute(() -> {
        int i = 1 / 0; // 發(fā)生異常
        return i;
    });
}

如上代碼,在線程池執(zhí)行任務(wù)時(shí),沒(méi)有添加異常處理。導(dǎo)致任務(wù)內(nèi)部發(fā)生異常時(shí),內(nèi)部錯(cuò)誤無(wú)法被記錄下來(lái)。

解決方法

在線程池執(zhí)行任務(wù)方法內(nèi)添加 try/catch 處理,代碼如下,

@Test
public void test() throws Exception {
    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
        5, 
        10, 
        60,
        TimeUnit.SECONDS, 
        new ArrayBlockingQueue<>(100000));
    Future<Integer> submit = threadPoolExecutor.execute(() -> {
        try {
            int i = 1 / 0;
            return i;
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return null;
        }
    });
}

優(yōu)雅的進(jìn)行線程池異常處理

當(dāng)線程池調(diào)用任務(wù)方法很多時(shí),那么每個(gè)線程池任務(wù)執(zhí)行的方法內(nèi)都要添加 try/catch 處理,這就不優(yōu)雅了,其實(shí) ThreadPoolExecutor 線程池類支持傳入 ThreadFactory 參數(shù)用于自定義線程工廠,這樣我們?cè)趧?chuàng)建線程時(shí),就可以指定 setUncaughtExceptionHandler 異常處理方法。

這樣就可以做到全局處理異常了,代碼如下,

ThreadFactory threadFactory = r -> {
    Thread thread = new Thread(r);
    thread.setUncaughtExceptionHandler((t, e) -> {
        // 記錄線程異常
        log.error(e.getMessage(), e);
    });
    return thread;
};
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
    5, 
    10, 
    60,
    TimeUnit.SECONDS, 
    new ArrayBlockingQueue<>(100000));
threadPoolExecutor.execute(() -> {
    log.info("---------------------");
    int i = 1 / 0;
});

不過(guò)要注意的是上面 setUncaughtExceptionHandler 方法只能針對(duì)線程池的 execute 方法來(lái)全局處理異常。對(duì)于線程池的 submit 方法是無(wú)法處理的。

坑二:拒絕策略設(shè)置錯(cuò)誤導(dǎo)致接口超時(shí)

在 Java 中,線程池拒絕策略可以說(shuō)一個(gè)常見(jiàn)八股文問(wèn)題。大家雖然都記住了線程池有四種決絕策略,可是實(shí)際代碼編寫(xiě)中,我發(fā)現(xiàn)大多數(shù)人都只會(huì)用 CallerRunsPolicy 策略(由調(diào)用線程處理任務(wù))。我吃過(guò)這個(gè)虧,因此也拿出來(lái)講講。

問(wèn)題原因

曾經(jīng)有一個(gè)線上業(yè)務(wù)接口使用了線程池進(jìn)行第三方接口調(diào)用,線程池配置里的拒絕策略采用的是 CallerRunsPolicy。示例代碼如下,

// 某個(gè)線上線程池配置如下
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
        50, // 最小核心線程數(shù)
        50, // 最大線程數(shù),當(dāng)隊(duì)列滿時(shí),能創(chuàng)建的最大線程數(shù)
        60L, TimeUnit.SECONDS, // 空閑線程超過(guò)核心線程時(shí),回收該線程的最大等待時(shí)間
        new LinkedBlockingQueue<>(5000), // 阻塞隊(duì)列大小,當(dāng)核心線程使用滿時(shí),新的線程會(huì)放進(jìn)隊(duì)列
        new CustomizableThreadFactory("task"), // 自定義線程名
        new ThreadPoolExecutor.CallerRunsPolicy() // 線程執(zhí)行的拒絕策略
);

threadPoolExecutor.execute(() -> {
    // 調(diào)用第三方接口
    ...
});

在第三方接口異常的情況下,線程池任務(wù)調(diào)用第三方接口一直超時(shí),導(dǎo)致核心線程數(shù)、最大線程數(shù)堆積被占滿、阻塞隊(duì)列也被占滿的情況下,也就會(huì)執(zhí)行拒絕策略,但是由于使用的是 CallerRunsPolicy 策略,導(dǎo)致線程任務(wù)直接由我們的業(yè)務(wù)線程來(lái)執(zhí)行。

因?yàn)榈谌浇涌诋惓#詷I(yè)務(wù)線程執(zhí)行也會(huì)繼繼續(xù)超時(shí),線上服務(wù)采用的 Tomcat 容器,最終也就導(dǎo)致 Tomcat 的最大線程數(shù)也被占滿,進(jìn)而無(wú)法繼續(xù)向外提供服務(wù)。

解決方法

首先我們要考慮業(yè)務(wù)接口的可用性,就算線程池任務(wù)被丟棄,也不應(yīng)該影響業(yè)務(wù)接口。

在業(yè)務(wù)接口穩(wěn)定性得到保證的情況下,在考慮到線程池任務(wù)的重要性,不是很重要的話,可以使用 DiscardPolicy 策略直接丟棄,要是很重要,可以考慮使用消息隊(duì)列來(lái)替換線程池。

坑三:重復(fù)創(chuàng)建線程池導(dǎo)致內(nèi)存溢出

不知道大家有沒(méi)有犯過(guò)這個(gè)問(wèn)題,不過(guò)我確實(shí)犯過(guò),歸根結(jié)底還是寫(xiě)代碼前,沒(méi)有思考好業(yè)務(wù)邏輯,直接動(dòng)手,寫(xiě)一步算一步 ??。所以說(shuō)寫(xiě)代碼的前的一些邏輯梳理、拆分、代碼設(shè)計(jì)很重要。

問(wèn)題原因

這個(gè)問(wèn)題的原因很簡(jiǎn)單,就是在一個(gè)方法內(nèi)重復(fù)創(chuàng)建了線程池,在執(zhí)行完之后卻沒(méi)有關(guān)閉。比較經(jīng)典的就是在定時(shí)任務(wù)內(nèi)使用線程池時(shí)有可能犯這個(gè)問(wèn)題,示例代碼如下,

@XxlJob("test")
public void test() throws Exception {
    // 某個(gè)線上線程池配置如下
    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
            50, // 最小核心線程數(shù)
            50, // 最大線程數(shù),當(dāng)隊(duì)列滿時(shí),能創(chuàng)建的最大線程數(shù)
            60L, TimeUnit.SECONDS, // 空閑線程超過(guò)核心線程時(shí),回收該線程的最大等待時(shí)間
            new LinkedBlockingQueue<>(5000), // 阻塞隊(duì)列大小,當(dāng)核心線程使用滿時(shí),新的線程會(huì)放進(jìn)隊(duì)列
            new CustomizableThreadFactory("task"), // 自定義線程名
            new ThreadPoolExecutor.CallerRunsPolicy() // 線程執(zhí)行的拒絕策略
    );
    threadPoolExecutor.execute(() -> {
        // 任務(wù)邏輯
        ...
    });
}

當(dāng)我們?cè)诙〞r(shí)任務(wù)中想使用線程池來(lái)縮短任務(wù)執(zhí)行時(shí)間時(shí),千萬(wàn)要注意別再任務(wù)內(nèi)創(chuàng)建了線程池,一旦犯了,基本都會(huì)在程序運(yùn)行一段時(shí)間后發(fā)現(xiàn)程序突然間就掛了,留下了一堆內(nèi)存 dump 報(bào)錯(cuò)的文件 ??。

解決方法

使用線程池單例,切勿重復(fù)創(chuàng)建線程池。示例代碼如下,

// 某個(gè)線上線程池配置如下
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
        50, // 最小核心線程數(shù)
        50, // 最大線程數(shù),當(dāng)隊(duì)列滿時(shí),能創(chuàng)建的最大線程數(shù)
        60L, TimeUnit.SECONDS, // 空閑線程超過(guò)核心線程時(shí),回收該線程的最大等待時(shí)間
        new LinkedBlockingQueue<>(5000), // 阻塞隊(duì)列大小,當(dāng)核心線程使用滿時(shí),新的線程會(huì)放進(jìn)隊(duì)列
        new CustomizableThreadFactory("task"), // 自定義線程名
        new ThreadPoolExecutor.CallerRunsPolicy() // 線程執(zhí)行的拒絕策略
);
@XxlJob("test")
public void test() throws Exception {
    threadPoolExecutor.execute(() -> {
        // 任務(wù)邏輯
        // ...
    });
}

坑四:共用線程池執(zhí)行不同類型任務(wù)導(dǎo)致效率低下

有時(shí)候,我們可能會(huì)想要節(jié)省線程資源,把不同類型的任務(wù)都放到同一個(gè)線程池中執(zhí)行,比如主要的業(yè)務(wù)邏輯和次要的日志記錄、監(jiān)控等。這看起來(lái)很合理,但是實(shí)際上,這樣做可能會(huì)導(dǎo)致一個(gè)任務(wù)影響另一個(gè)任務(wù),甚至導(dǎo)致死鎖的問(wèn)題。

問(wèn)題原因

問(wèn)題的原因是,不同類型的任務(wù)可能有不同的執(zhí)行時(shí)間、優(yōu)先級(jí)、依賴關(guān)系等,如果放到同一個(gè)線程池中,就可能會(huì)出現(xiàn)以下幾種情況:

  • 如果一個(gè)任務(wù)執(zhí)行時(shí)間過(guò)長(zhǎng),或者出現(xiàn)異常,那么它就會(huì)占用線程池中的一個(gè)線程,導(dǎo)致其他任務(wù)無(wú)法及時(shí)得到執(zhí)行,影響系統(tǒng)的吞吐量和響應(yīng)時(shí)間。
  • 如果一個(gè)任務(wù)的優(yōu)先級(jí)較低,或者不是很重要,那么它就可能搶占線程池中的一個(gè)線程,導(dǎo)致其他任務(wù)無(wú)法及時(shí)得到執(zhí)行,影響系統(tǒng)的可用性和正確性。
  • 如果一個(gè)任務(wù)依賴于另一個(gè)任務(wù)的結(jié)果,或者需要等待另一個(gè)任務(wù)的完成,那么它就可能造成線程池中的一個(gè)線程被阻塞,導(dǎo)致其他任務(wù)無(wú)法及時(shí)得到執(zhí)行,甚至導(dǎo)致死鎖的問(wèn)題。

解決方法

解決方法也很簡(jiǎn)單,就是使用不同的線程池來(lái)執(zhí)行不同類型的任務(wù),根據(jù)任務(wù)的特點(diǎn)和重要性來(lái)分配線程資源,避免一個(gè)任務(wù)影響另一個(gè)任務(wù)。具體來(lái)說(shuō),有以下幾個(gè)建議:

  • 對(duì)于主要的業(yè)務(wù)邏輯,使用一個(gè)專門(mén)的線程池,根據(jù)業(yè)務(wù)的并發(fā)度和響應(yīng)時(shí)間,設(shè)置合適的線程池參數(shù),保證業(yè)務(wù)的正常運(yùn)行和高效處理。
  • 對(duì)于次要的日志記錄、監(jiān)控等,使用一個(gè)單獨(dú)的線程池,根據(jù)任務(wù)的頻率和重要性,設(shè)置合適的線程池參數(shù),保證任務(wù)的異步執(zhí)行和不影響主業(yè)務(wù)。
  • 對(duì)于有依賴關(guān)系的任務(wù),使用一個(gè)單獨(dú)的線程池,根據(jù)任務(wù)的數(shù)量和復(fù)雜度,設(shè)置合適的線程池參數(shù),保證任務(wù)的有序執(zhí)行和不造成死鎖。

坑五:使用 ThreadLocal 和線程池的不兼容問(wèn)題

ThreadLocal 是 Java 提供的一個(gè)工具類,它可以讓每個(gè)線程擁有自己的變量副本,從而實(shí)現(xiàn)線程間的數(shù)據(jù)隔離,比如存儲(chǔ)一些線程相關(guān)的上下文信息,如用戶 ID、請(qǐng)求 ID 等。這看起來(lái)很有用,但是如果和線程池一起使用,就可能會(huì)出現(xiàn)一些意想不到的問(wèn)題,比如數(shù)據(jù)錯(cuò)亂、內(nèi)存泄漏等。

問(wèn)題原因

問(wèn)題的原因是,ThreadLocal 和線程池的設(shè)計(jì)理念是相悖的,ThreadLocal 是基于線程的,而線程池是基于任務(wù)的。具體來(lái)說(shuō),有以下幾個(gè)問(wèn)題:

  • ThreadLocal 的變量是綁定在線程上的,而線程池的線程是可以復(fù)用的,如果一個(gè)線程執(zhí)行完一個(gè)任務(wù)后,沒(méi)有清理 ThreadLocal 的變量,那么這個(gè)變量就會(huì)被下一個(gè)執(zhí)行的任務(wù)繼承,導(dǎo)致數(shù)據(jù)錯(cuò)亂的問(wèn)題。
  • ThreadLocal 的變量是存儲(chǔ)在 Thread 類的一個(gè) ThreadLocalMap 類型的屬性中的,這個(gè)屬性是一個(gè)弱引用的 Map,它的鍵是 ThreadLocal 對(duì)象,而值是變量的副本。如果 ThreadLocal 對(duì)象被回收,那么它的鍵就會(huì)失效,但是值還會(huì)保留在 Map 中,導(dǎo)致內(nèi)存泄漏的問(wèn)題。

解決方法

解決方法也很簡(jiǎn)單,就是在使用 ThreadLocal 和線程池的時(shí)候,注意以下幾點(diǎn):

  • 在使用 ThreadLocal 的變量之前,要確保為每個(gè)線程設(shè)置了正確的初始值,避免使用上一個(gè)任務(wù)的遺留值。
  • 在使用 ThreadLocal 的變量之后,要及時(shí)地清理 ThreadLocal 的變量,避免變量的副本被下一個(gè)執(zhí)行的任務(wù)繼承,或者占用內(nèi)存空間,導(dǎo)致內(nèi)存泄漏的問(wèn)題??梢允褂?try-finally 語(yǔ)句,或者使用 Java 8 提供的 AutoCloseable 接口,來(lái)實(shí)現(xiàn)自動(dòng)清理的功能。
  • 在使用 ThreadLocal 的時(shí)候,要注意線程池的大小和任務(wù)的數(shù)量,避免創(chuàng)建過(guò)多的 ThreadLocal 對(duì)象和變量的副本,導(dǎo)致內(nèi)存占用過(guò)大的問(wèn)題。可以使用一些工具,如 VisualVM,來(lái)監(jiān)控線程池和 ThreadLocal 的狀態(tài),及時(shí)發(fā)現(xiàn)和解決問(wèn)題。

總結(jié)

本文給大家介紹了線程池使用不當(dāng)?shù)奈鍌€(gè)坑,分別是線程池中異常消失、線程池決絕策略設(shè)置錯(cuò)誤、重復(fù)創(chuàng)建線程池導(dǎo)致內(nèi)存溢出、使用同一個(gè)線程池執(zhí)行不同類型的任務(wù)、使用 ThreadLocal 和線程池的不兼容問(wèn)題,以及它們的問(wèn)題原因和解決方法。希望這些內(nèi)容對(duì)大家有幫助。

責(zé)任編輯:武曉燕 來(lái)源: 程序員wayn
相關(guān)推薦

2024-04-10 08:39:56

BigDecimal浮點(diǎn)數(shù)二進(jìn)制

2015-03-24 16:29:55

默認(rèn)線程池java

2024-06-28 10:01:04

2021-09-11 19:00:54

Intro元素MemoryCache

2024-09-05 08:07:55

2023-01-18 23:20:25

編程開(kāi)發(fā)

2021-06-10 06:59:34

Redis應(yīng)用API

2022-06-21 11:24:05

多線程運(yùn)維

2024-08-26 08:29:55

2022-08-16 08:27:20

線程毀線程異步

2020-10-22 07:09:19

TCP網(wǎng)絡(luò)協(xié)議

2024-03-11 18:17:18

Python字符串分隔符

2025-04-29 10:17:42

2024-05-06 00:00:00

緩存高并發(fā)數(shù)據(jù)

2024-12-13 08:21:04

2009-12-17 14:53:52

VS2008程序

2021-05-20 10:02:50

系統(tǒng)Redis技巧

2021-08-26 14:26:25

Java代碼集合

2024-11-20 18:16:39

MyBatis批量操作數(shù)據(jù)庫(kù)

2017-07-17 15:46:20

Oracle并行機(jī)制
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)