【併發編程】ThreadLocal其實很簡單

来源:https://www.cnblogs.com/54chensongxia/archive/2019/11/11/11837975.html
-Advertisement-
Play Games

什麼是ThreadLocal ThreadLocal有點類似於Map類型的數據變數。ThreadLocal類型的變數每個線程都有自己的一個副本,某個線程對這個變數的修改不會影響其他線程副本的值。需要註意的是一個ThreadLocal變數,其中只能set一個值。 線上程1中初始化了一個ThreadLo ...


什麼是ThreadLocal

ThreadLocal有點類似於Map類型的數據變數。ThreadLocal類型的變數每個線程都有自己的一個副本,某個線程對這個變數的修改不會影響其他線程副本的值。需要註意的是一個ThreadLocal變數,其中只能set一個值。

ThreadLocal<String> localName = new ThreadLocal();
localName.set("name1");
String name = localName.get();

線上程1中初始化了一個ThreadLocal對象localName,並通過set方法,保存了一個值,同時線上程1中通過 localName.get()可以拿到之前設置的值,但是如果線上程2中,拿到的將是一個null。

下麵來看下ThreadLocal的源碼:

public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

可以發現,每個線程中都有一個 ThreadLocalMap數據結構,當執行set方法時,其值是保存在當前線程的 threadLocals變數中,當執行get方法中,是從當前線程的 threadLocals變數獲取。 (ThreadLocalMap的key值是ThreadLocal類型)

所以線上程1中set的值,對線程2來說是摸不到的,而且線上程2中重新set的話,也不會影響到線程1中的值,保證了線程之間不會相互干擾。

上面提到ThreadLoal的變數都是存儲在ThreadLoalMap的變數中,下麵給出下Thread、ThreadLoal和ThreadLoalMap的關係。

Thread類有屬性變數threadLocals (類型是ThreadLocal.ThreadLocalMap),也就是說每個線程有一個自己的ThreadLocalMap ,所以每個線程往這個ThreadLocal中讀寫隔離的,並且是互相不會影響的。一個ThreadLocal只能存儲一個Object對象,如果需要存儲多個Object對象那麼就需要多個ThreadLocal!

ThreadLocal使用場景

說完ThreadLocal的原理,我們來看看ThreadLocal的使用場景。

1. 保存線程上下文信息,在任意需要的地方可以獲取
比如我們在使用Spring MVC時,想要在Service層使用HttpServletRequest。一種方式就是在Controller層將這個變數傳給Service層,但是這種寫法不夠優雅。Spring早就幫我們想到了這種情況,而且提供了現成的工具類:

public static final HttpServletRequest getRequest(){
    HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
    return  request;
}

public static final HttpServletResponse getResponse(){
    HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
    return response;
}

上面的代碼就是使用ThreadLocal實現變數線上程各處傳遞的。

2. 保證某些情況下的線程安全,提升性能
性能監控,如記錄一下請求的處理時間,得到一些慢請求(如處理時間超過500毫秒),從而進行性能改進。這邊我們以Spring MVC的攔截器功能為列子。


public class StopWatchHandlerInterceptor extends HandlerInterceptorAdapter {  
    //NamedThreadLocal是Spring對ThreadLocal的封裝,原理一樣
    //在多線程情況下,startTimeThreadLocal變數必須每個線程之間隔離
    private NamedThreadLocal<Long>  startTimeThreadLocal = new NamedThreadLocal<Long>("StopWatch-StartTime");  
    @Override  
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,Object handler) throws Exception {  
        //1、開始時間
        long beginTime = System.currentTimeMillis();  
        //線程綁定變數(該數據只有當前請求的線程可見)  
        startTimeThreadLocal.set(beginTime);
        //繼續流程  
        return true;
    }  
      
    @Override  
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response,Object handler, Exception ex) throws Exception {  
        long endTime = System.currentTimeMillis();//2、結束時間  
        long beginTime = startTimeThreadLocal.get();//得到線程綁定的局部變數(開始時間)  
        long consumeTime = endTime - beginTime;//3、消耗的時間  
        if(consumeTime > 500) {//此處認為處理時間超過500毫秒的請求為慢請求  
        //TODO 記錄到日誌文件  
        System.out.println(String.format("%s consume %d millis", request.getRequestURI(), consumeTime));  
        }          
    }  
}  

說明:其實要實現上面的功能,完全可以不用ThreadLocal(同步鎖等),但是上面的代碼的確是說明ThreadLocal這個是用場景很好的列子。

ThreadLocal的最佳實踐

從上面的圖中可以看到,Entry的key指向ThreadLocal用虛線表示弱引用 ,下麵我們來看看ThreadLocalMap:

java對象的引用包括 : 強引用,軟引用,弱引用,虛引用 。

弱引用也是用來描述非必需對象的,當JVM進行垃圾回收時,無論記憶體是否充足,該對象僅僅被弱引用關聯,那麼就會被回收。當僅僅只有ThreadLocalMap中的Entry的key指向ThreadLocal的時候,ThreadLocal會進行回收的!!!
ThreadLocal被垃圾回收後,在ThreadLocalMap里對應的Entry的鍵值會變成null,但是Entry是強引用,那麼Entry裡面存儲的Object,並沒有辦法進行回收,所以ThreadLocalMap 存在記憶體泄露的風險。

所以最佳實踐,應該在我們不使用的時候,主動調用remove方法進行清理。這裡給出一個建議方案:


public class Dynamicxx {
    
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();

    public void dosomething(){
        try {
             contextHolder.set("name");
            // 其它業務邏輯
        } finally {
            contextHolder .remove();
        }
    }

}

參考

  • https://mp.weixin.qq.com/s/1ccG1R3ccP0_5A7A5zCdzQ
  • https://mp.weixin.qq.com/s/SNLNJcap8qmJF9r4IuY8LA

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一.使用的spring boot +mybatis-plus+shiro+maven來搭建項目框架 1 <!--shiro--> 2 <dependency> 3 <groupId>org.apache.shiro</groupId> 4 <artifactId>shiro-core</artifa ...
  • [TOC] 題目 "Largest Rectangle in a Histogram" 思路 單調棧。 不知道怎麼描述所以用樣例講一下。 我們可以用單調棧去維護每一個高度左右第一個比他矮的位置即可 $Code$ ...
  • 入門python一切都感覺到那麼簡單,從來沒有想過人生還可以有這麼美好的待遇,這一切都是因為接觸了python才讓我感到生活原來一切又充滿了希望 ...
  • jdk: 解壓: tar zxvf jdk-8u144-linux-x64.tar.gz 執行:vi /etc/profile export JAVA_HOME=/usr/local/jdk1.8.0_201 export CLASSPATH=$JAVA_HOME/lib export PATH=$ ...
  • Day1 考的不是很好,T1T2沒區分度,T3想的太少,考試後期幾乎都是在摸魚,bitset亂搞也不敢打,只拿到了35分,跟前面的差距很大 A. 最大或 標簽: 二進位+貪心 題解: 首先x,y中一定有一個是R,考慮L的取值:對於每一位分為x中有沒有討論: 1>有 如果這一位不加以後全加可以>=L則 ...
  • Scrapy.http.Request 自動去重,根據url的哈希值,進行去重 屬性 meta(dict) 在不同的請求之間傳遞數據,dict priority(int) 此請求的優先順序(預設為0) dont_filter(boolean) 關閉自動去重 errback(callable) 在處理請 ...
  • 一、環境準備 1. jdk1.8.1 做java開發的這個應該能自己找到 2.gradle-4.9 https://services.gradle.org/distributions/ 沒用過gradle的同學可以將其理解為類似於maven的包管理工具,這裡下載gradle-4.9-bin.zip, ...
  • 本文源碼: "GitHub·點這裡" || "GitEE·點這裡" 一、生活場景 1、場景描述 在公司的日常安排中,通常劃分多個部門,每個部門又會分為不同的小組,部門經理的一項核心工作就是協調部門小組之間的工作,例如開發小組,產品小組,小組的需求統一彙總到經理,經理統一安排和協調。 2、場景圖解 3 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...