0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

ThreadLocal基本內(nèi)容與用法

科技綠洲 ? 來源:Java技術(shù)指北 ? 作者:Java技術(shù)指北 ? 2023-10-13 11:39 ? 次閱讀

下面我們就來看看道哥都用的ThreadLocal。

1 ThreadLocal你來自哪里

Since: 1.2
Author: Josh Bloch and Doug Lea

又是并發(fā)大佬們的杰作,膜拜一下。怪不得道哥也愛用,自己設(shè)計的類總得用用。下面來看看基本內(nèi)容與用法吧。

2 ThreadLocal原理

“This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).”

“此類提供了thread-local變量。這些變量不同于普通的類似變量,因為訪問某個變量(通過其 get 或 set 方法)的每個線程都有自有的,獨立初始化的變量副本,ThreadLocal實例通常是希望將狀態(tài)與線程(例如,用戶ID或事務ID)關(guān)聯(lián)的類中的私有靜態(tài)字段。”

通過老爺子們的描述,指北君大概也知道了ThreadLocal的推薦使用場景,

  1. ThreadLocal提供了一種訪問某個特有變量的方法 訪問到的變量屬于當前線程,同一線程在任何地方都能訪問同一個線程特有變量。
  2. 推薦定義為 private static 類型,但是Doug Lea老爺子在ThreadLocalRandom 和 ReentrantReadWriteLock 中使用了 private static final 類型。(肯定是當年寫簡介的時候手抖了)

2.1 Thread中如何存儲

既然是線程的變量,自然是存在Thread對象中的一個變量了,但是它是通過ThreadLocal這個類來維護的。

//與此線程相關(guān)的ThreadLocal值,由ThreadLocal這個類維護
ThreadLocal.ThreadLocalMap threadLocals = null;

//與此線程相關(guān)的可繼承的ThreadLocal值,由InheritableThreadLocal類來維護
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

ThreadLocal中有一個內(nèi)部類來ThreadLocalMap來維護這些線程本地變量,

static class ThreadLocalMap {
        //初始容量,2的n次方
        private static final int INITIAL_CAPACITY = 16; 
        
        //根據(jù)需要調(diào)整數(shù)組大小,2的n次方
        private Entry[] table;

        //上面Entry數(shù)組中的元素數(shù)量
        private int size = 0;

        //The next size value at which to resize  Default to 0
        private int threshold; 
}

ThreadLocalMap中的Entry結(jié)構(gòu)如下,是一種key為弱引用(其目的就是Entry對象在GC時容易回收)的hash map,其中key總是ThreadLocal。

static class Entry extends WeakReference< ThreadLocal< ? >> {
    /** The value associated with this ThreadLocal. */
    Object value;
    Entry(ThreadLocal< ? > k, Object v) {
        super(k);
        value = v;
    }
}

2.2 常用方法 get,set,remove 詳解

  • get() 此方法是ThreadLocal最重要的方法之一,該方法返回此線程局部變量的當前線程副本中的值。大概可分為以下幾步:
    (1) 先獲取當前線程,然后再從線程中得到ThreadLocalMap。
    (2) 然后使用ThreadLocal對象的threadLocalHashCode進行散列計算,得到一個數(shù)組的index
    (3) 從Table數(shù)組中得到Entry,再對比Entry的key是不是和當前的ThreadLocal相等,如果相等就返回此Entry的value
    (4) 如果上一步中得到的Entry與當前ThreadLocal不相等,則會在方法getEntryAfterMiss中進行遍歷Entry數(shù)組table中的每一個元素,如果找不到就返回null。而且在遍歷的過程中會順便清理一下廢棄的Entry。

下面可以看一下get方法的具體代碼。

public T get() {
    //獲取當前線程
    Thread t = Thread.currentThread(); 

    //從當前線程中獲取ThreadLocalMap
    ThreadLocalMap map = getMap(t); 
    if (map != null) {
        
        //獲取map中當前ThreadLocal對象對應的entry
        ThreadLocalMap.Entry e = map.getEntry(this); 
        if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
        }
    }
    return setInitialValue();
}

private Entry getEntry(ThreadLocal< ? > key) {
    //散列計算得到Entry中當前的index
    int i = key.threadLocalHashCode & (table.length - 1);
    Entry e = table[i];
    
    //如果Entry不是null而且key等于當前
    // ThreadLocal對象則返回此Entry
    if (e != null && e.get() == key) 
        return e;
    else
        
        //Entry==null 或者其key不等于當前
        // ThreadLocal對象,遍歷其余Entry
        return getEntryAfterMiss(key, i, e);
}

private Entry getEntryAfterMiss(ThreadLocal< ? > key, int i, Entry e) {
    Entry[] tab = table;
    int len = tab.length;
    while (e != null) {
        ThreadLocal< ? > k = e.get();
        if (k == key) return e;
        if (k == null)
            //如果遍歷過程中發(fā)現(xiàn)有Entry的Key為Null,
            // 則清除掉作廢的Entry
            expungeStaleEntry(i);
        else
            //計算Entry數(shù)組下一個index
            i = nextIndex(i, len);
            e = tab[i];
        }
        return null;
}
  • set(T value) 此方法將此線程局部變量的當前線程副本中的值設(shè)置為指定值。

set線程本地變量步驟如下:
(1) 首先依然是獲取此線程的ThreadLocalMap
(2) Map不為null時往map中插入數(shù)據(jù),否側(cè)創(chuàng)建map并插入數(shù)據(jù)
(3) 具體的set方法依然是先遍歷Entry數(shù)組中所有的的Entry,然后依次對比每個Entry的key是否等于當前ThreadLocal,如果相等則直接替換現(xiàn)有Entry的value。如果Entry的Key為null,則立馬清理廢棄的Entry,并用新的Entry來替換此卡槽。
(4) 如果遍歷完都沒有return,則在在table中相應卡槽下新建Entry對象

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
 }
 
private void set(ThreadLocal< ? > key, Object value) {
    Entry[] tab = table;
    int len = tab.length;
    int i = key.threadLocalHashCode & (len-1);
    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {
        ThreadLocal< ? > k = e.get();
        
        //如果原Entry的key就是當前ThreadLocal對象,
        // 則直接替換現(xiàn)有value
        if (k == key) {     
            e.value = value;
            return;
        }
        if (k == null) {
            
    // 如果Entry的Key為null, 則直接替換為新的Entry            
            replaceStaleEntry(key, value, i); 
            return;
        }
    }
    // 如果前面的遍歷沒有return,
    // 則插入新的Entry對象到對應的卡槽    
    tab[i] = new Entry(key, value); 
    int sz = ++size;
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}
  • remove() remove則相對簡單,直接遍歷ThreadLocalMap中Entry數(shù)組table,找到對應的Entry,將Entry的key置為null,然后再清理相應的Entry。
private void remove(ThreadLocal< ? > key) {
    ...
    for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
        if (e.get() == key) {
            //Entry 的key置為null
            e.clear();
            // 清理對應卡槽,
            expungeStaleEntry(i); 
            return;
        }
    }
}

3 Java中使用的ThreadLocal

Java中有哪些源碼使用了ThreadLocal。

ThreadLocalRandom 中使用計算nextGaussian值時有使用到ThreadLocal。

InheritableThreadLocal繼承了ThreadLocal,線程中使用inheritableThreadLocals這個map存儲線程本地變量。和ThreadLocal的區(qū)別就是子線程依然可以訪問到父線程的線程本地變量,實際應用中也推薦InheritableThreadLocal

ReentrantReadWriteLock中線程讀寫鎖的計數(shù)器使用了ThreadLocal,其目的是記錄每個線程獲取讀寫鎖的次數(shù)

static final class ThreadLocalHoldCounter 
        extends ThreadLocal< HoldCounter > {
    public HoldCounter initialValue() {
        return new HoldCounter();
    }
}
//曾經(jīng)的Doug Lea老爺子推薦static field,
// 而他默默的使用了static final。

4 如何使用ThreadLocal

ThreadLocal非常適合存儲非線程安全的對象,并且不需要跨線程共享對象。很多需要線程隔離的操作都可以嘗試使用它。

ThreadLocal也非常適合在Web應用程序中使用,典型的應用就是在Web請求進來一開始就將請求狀態(tài)存儲在ThreadLocal中,然后參與處理的任何組件均可訪問該狀態(tài)。

以下是一個ThreadLocal示例:

具體使用就是配合interceptor或者filter在線程剛開始執(zhí)行的時候存儲SessionContext,線程執(zhí)行過程中可以隨時訪問該變量。然后在線程執(zhí)行結(jié)束的時候再調(diào)用remove()方法移除,防止內(nèi)存泄漏。

public class SessionContextHolder {
    private static final ThreadLocal< SessionContex > CONTEXHOLDER 
                    = new InheritableThreadLocal<  >();
    
    public static void remove(){CONTEXHOLDER.remove();};
    
    public static SessionContex get(){return CONTEXHOLDER.get();}
    
    public static void set(SessionContex sessionContex) {CONTEXHOLDER.set(sessionContex);}
}

總結(jié)

本文介紹了ThreadLocal的原理以及解析了常用方法的實現(xiàn)邏輯,以及在ThreadLocal一些應用。在一步步梳理的過程中,果然看到了以往忽略的各種細節(jié),最后給出了一個小Case。并發(fā)編程大神道哥.李都在用的ThreadLocal,不妨在自己的項目中偷偷用上,保證絲滑舒適。

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學習之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 存儲
    +關(guān)注

    關(guān)注

    13

    文章

    4172

    瀏覽量

    85483
  • 數(shù)組
    +關(guān)注

    關(guān)注

    1

    文章

    411

    瀏覽量

    25858
  • 線程
    +關(guān)注

    關(guān)注

    0

    文章

    502

    瀏覽量

    19614
收藏 人收藏

    評論

    相關(guān)推薦

    電機選型的基本內(nèi)容有哪些 電機選型需要哪些參數(shù)

    電機選型需要的基本內(nèi)容有:所驅(qū)動的負載類型、額定功率、額定電壓、額定轉(zhuǎn)速、其它條件。電機的種類有很多,在選電機的時候選一臺好的電機非常重要,那么電機選型的詳細步驟有哪些呢?下面我們來介紹一下電機選型
    發(fā)表于 08-05 10:29 ?1.1w次閱讀

    ThreadLocal實例應用

    ThreadLocal相信大家都用過,但你知道他的原理嗎,今天了不起帶大家學習ThreadLocal。 ThreadLocal是什么 在多線程編程中,經(jīng)常會遇到需要在不同線程中共享數(shù)據(jù)的情況
    的頭像 發(fā)表于 09-30 10:19 ?620次閱讀
    <b class='flag-5'>ThreadLocal</b>實例應用

    ThreadLocal的定義、用法及優(yōu)點

    ThreadLocal的定義、用法及其優(yōu)點。 ThreadLocal是Java中一個用來實現(xiàn)線程封閉技術(shù)的類。它提供了一個本地線程變量,可以在多線程環(huán)境下使每個線程都擁有自己的變量副本。每個線程都可以獨立地改變自己
    的頭像 發(fā)表于 09-30 10:14 ?933次閱讀
    <b class='flag-5'>ThreadLocal</b>的定義、<b class='flag-5'>用法</b>及優(yōu)點

    USS通信協(xié)議的基本內(nèi)容

    USS通信技術(shù)作為一種低成本的簡單驅(qū)動控制技術(shù),在工業(yè)現(xiàn)場有著廣泛的應用。今天這篇文章,我們就和大家一起聊聊USS通信協(xié)議的基本內(nèi)容
    發(fā)表于 01-19 06:45

    STM32單片機中需要用到的C語言知識有哪些

    STM32單片機中需要用到的C語言知識一、基本內(nèi)容二、疑問點1.聲明變量2.預處理一、基本內(nèi)容二、疑問點1.聲明變量const:可創(chuàng)建全局常量 局部常量, 數(shù)字常量, 數(shù)組常量 結(jié)構(gòu)常量. 用法
    發(fā)表于 07-15 09:24

    課程實驗指導書格式,具體項目指導書格式與基本內(nèi)容要求

    附:1.課程實驗指導書封面格式2.課程實驗指導書前言內(nèi)容要求3.具體項目指導書格式與基本內(nèi)容要求4.學生實驗報告基本內(nèi)容要求具體項目指導書格式與基本內(nèi)容
    發(fā)表于 09-24 11:39 ?0次下載

    《電視技術(shù)》課程基本內(nèi)容及要求(電子專業(yè))

    《電視技術(shù)》課程基本內(nèi)容及要求(電子專業(yè))本課程教學時數(shù)為48課時,教學內(nèi)容及課程要求如下1、廣播電視的基本知識熟悉光電轉(zhuǎn)換過程;電視掃描原理;掌握廣播電
    發(fā)表于 03-12 09:31 ?17次下載

    電子線路CAA的基本內(nèi)容及發(fā)展動態(tài)

    本文綜述了電子線路計算機輔助分析(CAA)的基本內(nèi)容和方法;提出了這一領(lǐng)域目前正在研究的有關(guān)課題。關(guān)鍵詞:大規(guī)模網(wǎng)絡分析;非線性網(wǎng)絡分析;開關(guān)電容網(wǎng)絡分析;計
    發(fā)表于 06-01 13:31 ?15次下載

    基爾霍夫定律的基本內(nèi)容是什么?

    基爾霍夫定律的基本內(nèi)容是什么?基爾霍夫定律是電路分析和計算的重要工具。依據(jù)它可以作出決定電路特性的電流方程式和電壓方程式?;鶢柣舴蚨捎址譃榈?/div>
    發(fā)表于 10-04 15:08 ?9423次閱讀
    基爾霍夫定律的<b class='flag-5'>基本內(nèi)容</b>是什么?

    電子測量的基本內(nèi)容

    電子測量的基本內(nèi)容 隨著科學技術(shù)的不斷發(fā)展,測量的內(nèi)容越來越多。電參數(shù)的測量分為電磁測量和電子測量兩類,前者著重研究交
    發(fā)表于 07-11 15:23 ?3026次閱讀

    液壓傳動系統(tǒng)設(shè)計的基本內(nèi)容和一般流程

    本問介紹了液壓傳動系統(tǒng)的形式及禁忌、詳細的介紹了液壓傳動系統(tǒng)設(shè)計的基本內(nèi)容和一般流程,最后介紹了液壓控制禁忌及分支管路的功率分配問題。
    發(fā)表于 01-04 13:56 ?8911次閱讀
    液壓傳動系統(tǒng)設(shè)計的<b class='flag-5'>基本內(nèi)容</b>和一般流程

    AVR單片機教程之SPI的用法程序資料說明

    關(guān)于SPI的一些基本內(nèi)容就不再在這說了,下面主要是一些實用的用法知識。SPI是全雙工通信,即可以單工通信,又可以全雙工通信。在單工通信和半雙工通信,比較簡單,就是主機(從機)發(fā)送數(shù)據(jù),對方接受數(shù)據(jù)。
    發(fā)表于 10-23 18:57 ?5次下載
    AVR單片機教程之SPI的<b class='flag-5'>用法</b>程序資料說明

    ThreadLocal發(fā)生內(nèi)存泄漏的原因

    前言 ThreadLocal 的作用是提供線程內(nèi)的局部變量,這種變量在線程的生命周期內(nèi)起作用,減少同一個線程內(nèi)多個函數(shù)或者組件之間一些公共變量的傳遞的復雜度。但是如果濫用 ThreadLocal
    的頭像 發(fā)表于 05-05 16:23 ?3637次閱讀

    如何使用ThreadLocal來避免內(nèi)存泄漏

    本次給大家介紹重要的工具ThreadLocal。講解內(nèi)容如下,同時介紹什么場景下發(fā)生內(nèi)存泄漏,如何復現(xiàn)內(nèi)存泄漏,如何正確使用它來避免內(nèi)存泄漏。 ThreadLocal是什么?有哪些用途
    的頭像 發(fā)表于 08-20 09:29 ?4165次閱讀
    如何使用<b class='flag-5'>ThreadLocal</b>來避免內(nèi)存泄漏

    ThreadLocal源碼解析及實戰(zhàn)應用

    ThreadLocal 是一個關(guān)于創(chuàng)建線程局部變量的類。
    的頭像 發(fā)表于 01-29 14:53 ?426次閱讀