欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

程序員最喜歡的ThreadLocal使用姿勢(shì)

 更新時(shí)間:2022年02月06日 10:23:17   作者:qiaoHaoTing  
ThreadLocal并不是一個(gè)Thread,而是Thread的局部變量,也許把它命名為ThreadLocalVariable更容易讓人理解一些,下面這篇文章主要給大家介紹了程序員最喜歡的ThreadLocal使用姿勢(shì),需要的朋友可以參考下

一、常見場(chǎng)景

1、ThreadLocal作為線程上下文副本,那么一種最常見的使用方式就是用來(lái)方法隱式傳參,通過(guò)提供的set()和get()兩個(gè)public方法來(lái)實(shí)現(xiàn)在不同的方法中的參數(shù)傳遞。對(duì)于編程規(guī)范來(lái)說(shuō),方法定義的時(shí)候是對(duì)參數(shù)個(gè)數(shù)是有限制的,甚至在一些大廠,對(duì)方法參數(shù)個(gè)數(shù)是有明確規(guī)定的。

2、線程安全,每個(gè)線程維持自己的變量,以免紊亂,像常用的數(shù)據(jù)庫(kù)的連接池的線程安全實(shí)現(xiàn)就使用了ThreadLocal。

二、進(jìn)階使用

以參數(shù)傳遞為例子,如何更好地使用ThreadLocal來(lái)實(shí)現(xiàn)在同一線程棧中不同方法中的參數(shù)傳遞。在參數(shù)傳遞的時(shí)候,那么都會(huì)有參數(shù)名,參數(shù)值,而ThreadLocal提供的get()和set()方法,不能直接滿足設(shè)置參數(shù)名和參數(shù)值。這種情況下就需要對(duì)ThreadLocal進(jìn)一次封裝,如下代碼,維護(hù)一個(gè)map對(duì)象,然后提供setValue(key, value)和getValue(key, value)方法,就可以很方便地實(shí)現(xiàn)了參數(shù)的設(shè)置和獲?。辉谛枰牡胤綄?duì)參數(shù)進(jìn)行清理,使用remove(key)或者clear()即可實(shí)現(xiàn)。

import java.util.HashMap;
import java.util.Map;
 
public class ThreadLocalManger<T> extends ThreadLocal<T> {
 
    private static ThreadLocalManger<Map<String, Object>> MANGER = new ThreadLocalManger<>();
 
    private static HashMap<String, Object> MANGER_MAP = new HashMap<>();
 
    public static void setValue(String key, Object value) {
        Map<String, Object> context = MANGER.get();
        if(context == null) {
            synchronized (MANGER_MAP) {
                if(context == null) {
                    context = new HashMap<>();
                    MANGER.set(context);
                }
            }
        }
        context.put(key, value);
    }
 
    public static Object getValue(String key) {
        Map<String, Object> context = MANGER.get();
        if(context != null) {
            return context.get(key);
        }
        return null;
    }
 
    public static void remove(String key) {
        Map<String, Object> context = MANGER.get();
        if(context != null) {
            context.remove(key);
        }
    }
 
    public static void clear() {
        Map<String, Object> context = MANGER.get();
        if(context != null) {
            context.clear();
        }
    }
}

三、使用漏洞

繼續(xù)以參數(shù)傳遞為例子,來(lái)看看ThreadLocal使用過(guò)程中存在的問(wèn)題和后果。在實(shí)際業(yè)務(wù)的功能開發(fā)中,為了提升效率,大部分情況下都會(huì)使用線程池來(lái)實(shí)現(xiàn),比如數(shù)據(jù)庫(kù)的連接池、RPC請(qǐng)求連接池、MQ消息處理池、后臺(tái)批量job池等等;同時(shí)也可能會(huì)使用一個(gè)伴隨整個(gè)應(yīng)用生命周期的線程(守護(hù)線程)來(lái)實(shí)現(xiàn)的一些功能,比如說(shuō)心跳、監(jiān)控等等。使用線程池,那么在實(shí)際生產(chǎn)業(yè)務(wù)中并發(fā)肯定不低,池中線程就會(huì)一直復(fù)用;守護(hù)線程一旦創(chuàng)建,那么就會(huì)活到應(yīng)用停機(jī)。所以在這些情況下,線程的生命周期很長(zhǎng),在使用ThreadLocal的時(shí)候,一定要進(jìn)行清理,不然就會(huì)有內(nèi)存溢出的情況發(fā)生。通過(guò)以下案例來(lái)模擬內(nèi)存溢出的情況。

通過(guò)一個(gè)死循環(huán)來(lái)模擬高并發(fā)場(chǎng)景。創(chuàng)建一個(gè)10個(gè)核心線程數(shù),10個(gè)最大線程數(shù)數(shù),60秒空閑時(shí)間的、線程名以ThreadLocal-demo-開頭的線程池,在該場(chǎng)景下,將有10個(gè)線程來(lái)運(yùn)行,運(yùn)行內(nèi)容很簡(jiǎn)單:生成一個(gè)UUID,并將其作為參數(shù)key,然后設(shè)置到線程副本中。

import org.springframework.scheduling.concurrent.CustomizableThreadFactory;
import org.springframework.stereotype.Service;
 
import java.util.UUID;
import java.util.concurrent.*;
 
@Service
public class ThreadLocalService {
 
    ThreadFactory springThreadFactory = new CustomizableThreadFactory("TheadLocal-demo-");
 
    ExecutorService executorService = new ThreadPoolExecutor(10, 10, 60,
            TimeUnit.SECONDS, new LinkedBlockingQueue<>(), springThreadFactory);
 
    ExecutorService service = new ThreadPoolExecutor(10, 10, 60,
            TimeUnit.SECONDS, new LinkedBlockingQueue<>());
 
    public Object setValue() {
        for(; ;) {
            try {
                Runnable runnable = new Runnable() {
                    @Override
                    public void run() {
                        String id = UUID.randomUUID().toString();
                        // add
                        ThreadLocalManger.setValue(id, "this is a value");
                        //do something here
                        ThreadLocalManger.getValue(id);
                        // clear()
                        //ThreadLocalManger.clear();
                    }
                };
                executorService.submit(runnable);
            } catch (Exception e) {
                e.printStackTrace();
                break;
            }
        }
        return "success";
    }
 
}

以上代碼中已把clear()方法注釋掉,不做清理,觸發(fā)程序,稍微將jvm設(shè)置低一些,跑不久就會(huì)報(bào)如下OOM。

java.lang.OutOfMemoryError: GC overhead limit exceeded
Exception in thread "TheadLocal-demo-9" 
Exception in thread "TheadLocal-demo-8" 
Exception in thread "TheadLocal-demo-6" 
Exception in thread "TheadLocal-demo-10" 
Exception in thread "TheadLocal-demo-7" 
java.lang.OutOfMemoryError: GC overhead limit exceeded
Exception in thread "TheadLocal-demo-5" 
java.lang.OutOfMemoryError: GC overhead limit exceeded
java.lang.OutOfMemoryError: GC overhead limit exceeded
java.lang.OutOfMemoryError: GC overhead limit exceeded
    at com.intellij.rt.debugger.agent.CaptureStorage.insertEnter(CaptureStorage.java:57)
    at java.util.concurrent.FutureTask.run(FutureTask.java)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at java.lang.Thread.run(Thread.java:748)
java.lang.OutOfMemoryError: GC overhead limit exceeded
java.lang.OutOfMemoryError: GC overhead limit exceeded

就會(huì)發(fā)生嚴(yán)重的內(nèi)存溢出,通過(guò)如下debug截圖可知,設(shè)置進(jìn)去的UUID堆積在內(nèi)存中,逐步變多,最終撐爆內(nèi)存。

 在實(shí)際的業(yè)務(wù)場(chǎng)景中,需要傳遞的可能有訂單號(hào),交易號(hào),流水號(hào)等等,這些變量往往是唯一不重復(fù)的、符合案例中的UUID情況,在不清理的情況下就會(huì)造成應(yīng)用OOM,進(jìn)而不可用;在分布式系統(tǒng)中,還能導(dǎo)致上下游系統(tǒng)不可用,進(jìn)而導(dǎo)致整個(gè)分布式進(jìn)去的不可用;如果這些信息往往還可能用在網(wǎng)絡(luò)傳輸中,大消息占有網(wǎng)絡(luò)帶寬,嚴(yán)重甚至導(dǎo)致網(wǎng)絡(luò)癱瘓。所以一個(gè)小小的細(xì)節(jié)就會(huì)置整個(gè)集群于危險(xiǎn)之中,那么如何合理化解呢。

四、終階使用

以上問(wèn)題在于忘記清理,那么如何讓清理無(wú)感知,即不需要清理也沒有問(wèn)題。根因在于線程跑完一次之后,沒有進(jìn)行清理,所以可提供一個(gè)基類線程,在線程執(zhí)行最后對(duì)清理進(jìn)行封裝。如下代碼。提供一個(gè)BaseRunnable抽象基類,該類主要如下特點(diǎn)。

        1、該類繼承Runnable。

        2、實(shí)現(xiàn)setArg(key, value)和getArg(key)兩個(gè)方法。

        2、在重寫的run方式中分為兩步,第一步,調(diào)用抽象方法task;第二步,清理線程副本。

有了以上3個(gè)特點(diǎn),繼承了BaseRunnable的線程類,只需要在實(shí)現(xiàn)task方法,在task方法中實(shí)現(xiàn)業(yè)務(wù)邏輯,參數(shù)傳遞和獲取通過(guò)setArg(key, value)和getArg(key)兩個(gè)方法即可實(shí)現(xiàn),無(wú)需再顯示清理。

public abstract class BaseRunnable implements Runnable {
 
    @Override
    public void run() {
        try {
            task();
        } finally {
            ThreadLocalManger.clear();
        }
    }
 
    public void setArg(String key, String value) {
        ThreadLocalManger.setValue(key, value);
    }
 
    public Object getArg(String key) {
        return ThreadLocalManger.getValue(key);
    }
 
    public abstract void task();
}

總結(jié)

到此這篇關(guān)于ThreadLocal使用姿勢(shì)的文章就介紹到這了,更多相關(guān)ThreadLocal使用姿勢(shì)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 如何使用Guava Cache做緩存

    如何使用Guava Cache做緩存

    Cache在ConcurrentHashMap的基礎(chǔ)上提供了自動(dòng)加載數(shù)據(jù)、清除數(shù)據(jù)、get-if-absend-compute的功能,本文給大家介紹如何使用Guava Cache做緩存,感興趣的朋友一起看看吧
    2023-11-11
  • java中流的使用

    java中流的使用

    本文主要介紹了java中流的使用以及分類。具有一定的參考價(jià)值,下面跟著小編一起來(lái)看下吧
    2017-01-01
  • SpringSecurity中的UserDetails和UserDetailsService接口詳解

    SpringSecurity中的UserDetails和UserDetailsService接口詳解

    這篇文章主要介紹了SpringSecurity中的UserDetails和UserDetailsService接口詳解,UserDetailsService 在 Spring Security 中主要承擔(dān)查詢系統(tǒng)內(nèi)用戶、驗(yàn)證密碼、封裝用戶信息和角色權(quán)限,需要的朋友可以參考下
    2023-11-11
  • 使用dom4j解析xml文件問(wèn)題

    使用dom4j解析xml文件問(wèn)題

    這篇文章主要介紹了使用dom4j解析xml文件問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-09-09
  • SpringBoot中的健康檢查詳解

    SpringBoot中的健康檢查詳解

    這篇文章主要介紹了SpringBoot中的健康檢查詳解,健康檢查是一種用來(lái)確保應(yīng)用程序和其所依賴的服務(wù)的狀態(tài)正常的機(jī)制,在本文中,我們將探討SpringBoot中的健康檢查是什么以及如何使用它來(lái)監(jiān)視應(yīng)用程序的狀態(tài),需要的朋友可以參考下
    2023-07-07
  • springboot的pom.xml配置方式

    springboot的pom.xml配置方式

    這篇文章主要介紹了springboot的pom.xml配置方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-12-12
  • java byte數(shù)組與int,long,short,byte的轉(zhuǎn)換實(shí)現(xiàn)方法

    java byte數(shù)組與int,long,short,byte的轉(zhuǎn)換實(shí)現(xiàn)方法

    下面小編就為大家?guī)?lái)一篇java byte數(shù)組與int,long,short,byte的轉(zhuǎn)換實(shí)現(xiàn)方法。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2016-10-10
  • 一文帶你搞懂Java定時(shí)器Timer的使用

    一文帶你搞懂Java定時(shí)器Timer的使用

    定時(shí)器類似于我們生活中的鬧鐘,可以設(shè)定一個(gè)時(shí)間來(lái)提醒我們。而定時(shí)器是指定一個(gè)時(shí)間去執(zhí)行一個(gè)任務(wù),讓程序去代替人工準(zhǔn)時(shí)操作。本文就來(lái)聊聊Java定時(shí)器Timer的使用,需要的可以參考一下
    2023-01-01
  • Struts2實(shí)現(xiàn)CRUD(增 刪 改 查)功能實(shí)例代碼

    Struts2實(shí)現(xiàn)CRUD(增 刪 改 查)功能實(shí)例代碼

    CRUD是Create(創(chuàng)建)、Read(讀取)、Update(更新)和Delete(刪除)的縮寫,它是普通應(yīng)用程序的縮影。接下來(lái)通過(guò)本文給大家介紹Struts2實(shí)現(xiàn)CRUD(增 刪 改 查)功能實(shí)例代碼,感興趣的朋友一起看看吧
    2016-06-06
  • spring boot與kafka集成的簡(jiǎn)單實(shí)例

    spring boot與kafka集成的簡(jiǎn)單實(shí)例

    本篇文章主要介紹了spring boot與kafka集成的簡(jiǎn)單實(shí)例,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-09-09

最新評(píng)論