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

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

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

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

科技綠洲 ? 來(lái)源:Java技術(shù)指北 ? 作者:Java技術(shù)指北 ? 2023-09-30 10:14 ? 次閱讀

ThreadLocal

簡(jiǎn)介

ThreadLocal是Java中一個(gè)非常重要的線程技術(shù)。它可以讓每個(gè)線程都擁有自己的變量副本,避免了線程間的競(jìng)爭(zhēng)和數(shù)據(jù)泄露問(wèn)題。在本文中,我們將詳細(xì)介紹ThreadLocal的定義、用法及其優(yōu)點(diǎn)。

ThreadLocal是Java中一個(gè)用來(lái)實(shí)現(xiàn)線程封閉技術(shù)的類。它提供了一個(gè)本地線程變量,可以在多線程環(huán)境下使每個(gè)線程都擁有自己的變量副本。每個(gè)線程都可以獨(dú)立地改變自己的副本,而不會(huì)影響到其他線程的副本。ThreadLocal的實(shí)現(xiàn)是基于ThreadLocalMap的,每個(gè)ThreadLocal對(duì)象 都對(duì)應(yīng)一個(gè)ThreadLocalMap,其中存儲(chǔ)了線程本地變量的值。

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

ThreadLocal的主要優(yōu)點(diǎn)是可以提高并發(fā)程序的性能和安全性,同時(shí)也存在一些缺點(diǎn)和使用場(chǎng)景需要注意。

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

  1. 提高并發(fā)性能:使用ThreadLocal可以避免多個(gè)線程之間的競(jìng)爭(zhēng),從而提高程序的并發(fā)性能。
  2. 保證線程安全:每個(gè)線程有自己獨(dú)立的變量副本,避免了線程安全問(wèn)題。
  3. 簡(jiǎn)化代碼:使用ThreadLocal可以避免傳遞參數(shù)的繁瑣,簡(jiǎn)化代碼。

缺點(diǎn):

  1. 內(nèi)存泄漏:ThreadLocal變量副本的生命周期與線程的生命周期一樣長(zhǎng),如果線程長(zhǎng)時(shí)間存在,而ThreadLocal變量沒(méi)有及時(shí)清理,就會(huì)造成內(nèi)存泄漏。
  2. 增加資源開(kāi)銷(xiāo):每個(gè)線程都要?jiǎng)?chuàng)建一個(gè)獨(dú)立的變量副本,如果線程數(shù)很多,就會(huì)增加資源開(kāi)銷(xiāo)。
  3. 不適用于共享變量:ThreadLocal適用于每個(gè)線程有獨(dú)立的變量副本的場(chǎng)景,不適用于共享變量的場(chǎng)景。

適用場(chǎng)景

  1. 線程安全的對(duì)象:ThreadLocal適用于需要在多個(gè)線程中使用的線程安全對(duì)象,例如SimpleDateFormat、Random等。
  2. 跨層傳遞參數(shù):ThreadLocal可以避免在方法之間傳遞參數(shù)的繁瑣,尤其在跨層傳遞參數(shù)的場(chǎng)景中,可以大大簡(jiǎn)化代碼。
  3. 線程局部變量:ThreadLocal可以用于在當(dāng)前線程中存儲(chǔ)和訪問(wèn)局部變量,例如日志、請(qǐng)求信息等。

實(shí)現(xiàn)原理

首先通過(guò)一張圖看下ThreadLocal與線程的關(guān)系圖:

圖片

  1. 每個(gè)Thread對(duì)象都有一個(gè)ThreadLocalMap類型的成員變量threadLocals,這個(gè)變量是一個(gè)鍵值對(duì)集合,用于存儲(chǔ)每個(gè)ThreadLocal對(duì)象對(duì)應(yīng)的值。
  2. 每個(gè)ThreadLocal對(duì)象都有一個(gè)唯一的ID,用于在ThreadLocalMap中作為鍵來(lái)存儲(chǔ)值。
  3. 當(dāng)一個(gè)線程第一次調(diào)用ThreadLocal對(duì)象的get()方法時(shí),它會(huì)先獲取當(dāng)前線程的ThreadLocalMap對(duì)象,然后以ThreadLocal對(duì)象的ID作為鍵,從ThreadLocalMap中獲取對(duì)應(yīng)的值。
  4. 如果ThreadLocalMap中不存在對(duì)應(yīng)的鍵值對(duì),則調(diào)用ThreadLocal對(duì)象的initialValue()方法來(lái)初始化一個(gè)值,并將其存儲(chǔ)到ThreadLocalMap中。
  5. 如果ThreadLocalMap對(duì)象的引用不再需要,那么需要手動(dòng)將其置為null,這樣可以避免內(nèi)存泄漏。

內(nèi)存泄漏:

ThreadLocal變量副本的生命周期與線程的生命周期一樣長(zhǎng),如果線程長(zhǎng)時(shí)間存在,而ThreadLocal變量沒(méi)有及時(shí)清理,就會(huì)造成內(nèi)存泄漏。為了避免內(nèi)存泄漏,可以在使用ThreadLocal的地方及時(shí)清理ThreadLocal變量,例如在線程池中使用ThreadLocal時(shí),需要在線程結(jié)束時(shí)手動(dòng)清理ThreadLocal變量。

內(nèi)存泄漏出現(xiàn)的原因:

ThreadLocalMap中的Entry對(duì)象持有ThreadLocal對(duì)象的弱引用,但是ThreadLocalMap中的Entry對(duì)象是由ThreadLocal對(duì)象強(qiáng)引用的。
如果ThreadLocal對(duì)象沒(méi)有及時(shí)清理,在ThreadLocal對(duì)象被垃圾回收時(shí),ThreadLocalMap中的Entry對(duì)象仍然存在,從而導(dǎo)致內(nèi)存泄漏。

解決內(nèi)存泄漏的方法:

在使用ThreadLocal的代碼中及時(shí)清理ThreadLocal變量。通常情況下,我們可以使用ThreadLocal的remove()方法手動(dòng)清理ThreadLocal
變量,或者在使用完ThreadLocal變量后將其設(shè)置為null

圖片

通過(guò)上圖我們可以看到,在線程方法執(zhí)行過(guò)程中,ThreadLocal、ThreadLocalMap以及Thread之間的引用關(guān)系; Thread中存在一個(gè)屬性threadLocals指向了ThreadLocalMap,ThreadLocal實(shí)現(xiàn)線程級(jí)別的數(shù)據(jù)隔離主要是基于該對(duì)象;在ThreadLocal中是沒(méi)有存儲(chǔ)任何數(shù)據(jù),其更像一個(gè)線程與ThreadLocalMap間的協(xié)調(diào)器,數(shù)據(jù)存儲(chǔ)在ThreadLocalMap中,但是該Map的Key卻是ThreadLocal的弱引用;

一般情況下,線程執(zhí)行完成后,待線程銷(xiāo)毀,那么線程對(duì)應(yīng)的屬性threadLocals也會(huì)被銷(xiāo)毀;但是真實(shí)環(huán)境中對(duì)線程的使用大部分都是線程池,這樣在整個(gè)系統(tǒng)生命周期中, 線程都是有效的,直至線程池關(guān)閉。而將ThreadLocalMap的Key設(shè)置成弱引用時(shí),經(jīng)過(guò)GC后該Map的Key則變成了null,但是其Value卻一直存在,因此需要手動(dòng)將key為null 的數(shù)據(jù)進(jìn)行清理。

下面是一個(gè)示例演示如何避免ThreadLocal內(nèi)存泄漏:

public class MyThreadLocal {
    
    private static ThreadLocal< String > threadLocal = new ThreadLocal<  >();

    public static void set(String value) {
        threadLocal.set(value);
    }

    public static String get() {
        return threadLocal.get();
    }

    public static void remove() {
        threadLocal.remove();
    }
}

public class MyRunnable implements Runnable {
    
    @Override
    public void run() {
        MyThreadLocal.set("hello");
        System.out.println(MyThreadLocal.get());
        // 在使用完ThreadLocal變量后,調(diào)用remove()方法清理ThreadLocal變量
        MyThreadLocal.remove();
    }
}

在上面的代碼中,MyThreadLocal類封裝了ThreadLocal變量的操作,MyRunnable類實(shí)現(xiàn)了Runnable接口,使用MyThreadLocal類來(lái)存儲(chǔ)和訪問(wèn) ThreadLocal變量。在MyRunnable的run()方法中,使用完ThreadLocal變量后,調(diào)用remove()方法清理ThreadLocal變量,避免了內(nèi)存泄漏的問(wèn)題。


ThreadLocal一般會(huì)設(shè)置成static

主要是為了避免重復(fù)創(chuàng)建TSO(thread specific object,即與線程相關(guān)的變量。)我們知道,一個(gè)ThreadLocal實(shí)例對(duì)應(yīng)當(dāng)前線程中的一個(gè)TSO實(shí)例。如果把ThreadLocal聲明為某個(gè)類的實(shí)例變量(而不是靜態(tài)變量),那么每創(chuàng)建一個(gè)該類的實(shí)例就會(huì)導(dǎo)致一個(gè)新的TSO實(shí)例被創(chuàng)建。而這些被創(chuàng)建的TSO實(shí)例是同一個(gè)類的實(shí)例。同一個(gè)線程可能會(huì)訪問(wèn)到同一個(gè)TSO(指類)的不同實(shí)例,這即便不會(huì)導(dǎo)致錯(cuò)誤,也會(huì)導(dǎo)致浪費(fèi)!

簡(jiǎn)單的說(shuō)就是在ThreadLocalMap中,同一個(gè)線程是否有必要設(shè)置多個(gè)ThreadLocal來(lái)存儲(chǔ)線程變量?

示例

下面是一個(gè)簡(jiǎn)單的例子,演示了如何使用ThreadLocal來(lái)實(shí)現(xiàn)線程數(shù)據(jù)隔離:

public class ThreadLocalTest {
    
    private static ThreadLocal< String > threadLocal = new ThreadLocal<  >();

    public static void main(String[] args) throws InterruptedException {
        new Thread(() - > {
            threadLocal.set("Thread A");
            System.out.println("Thread A: " + threadLocal.get());
        }).start();

        new Thread(() - > {
            threadLocal.set("Thread B");
            System.out.println("Thread B: " + threadLocal.get());
        }).start();

        Thread.sleep(1000);

        System.out.println("Main: " + threadLocal.get());
    }
}

運(yùn)行結(jié)果如下:

Thread A: Thread A
Thread B: Thread B
Main: null

從輸出結(jié)果可以看出,每個(gè)線程都擁有自己的變量副本,互不影響。而在主線程中,由于沒(méi)有設(shè)置過(guò)變量副本,所以返回null。

結(jié)束語(yǔ)

ThreadLocal是幫助我們?cè)诙鄠€(gè)線程間實(shí)現(xiàn)線程對(duì)數(shù)據(jù)獨(dú)享,并不是用來(lái)解決線程間的數(shù)據(jù)共享問(wèn)題。

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

    關(guān)注

    13

    文章

    4323

    瀏覽量

    85922
  • JAVA
    +關(guān)注

    關(guān)注

    19

    文章

    2970

    瀏覽量

    104834
  • 程序
    +關(guān)注

    關(guān)注

    117

    文章

    3789

    瀏覽量

    81134
  • 多線程技術(shù)
    +關(guān)注

    關(guān)注

    0

    文章

    12

    瀏覽量

    8567
收藏 人收藏

    評(píng)論

    相關(guān)推薦

    SQLx的基礎(chǔ)用法和進(jìn)階用法

    SQLx是一個(gè)Rust語(yǔ)言的異步SQL數(shù)據(jù)庫(kù)訪問(wèn)庫(kù),支持多種數(shù)據(jù)庫(kù),包括PostgreSQL、MySQL、SQLite等。本教程將以SQLite為例,介紹SQLx的基礎(chǔ)用法和進(jìn)階用法。 基礎(chǔ)用法
    的頭像 發(fā)表于 09-19 14:29 ?2366次閱讀

    Stream模塊的基礎(chǔ)用法和進(jìn)階用法

    有用。在本教程中,我們將介紹 Stream 模塊的基礎(chǔ)用法和進(jìn)階用法,并提供示例。 基礎(chǔ)用法 在本節(jié)中,我們將介紹 Stream 模塊的基礎(chǔ)用法,并提供基礎(chǔ)示例。 從 Vec 中創(chuàng)建
    的頭像 發(fā)表于 09-19 15:33 ?1219次閱讀

    ThreadLocal實(shí)例應(yīng)用

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

    用戶自定義終止符EOF用法

    EOF(End OF File)在Linux命令和腳本中表示用戶自定義終止符,其用法如下:
    發(fā)表于 07-23 07:18

    狀態(tài)機(jī)原理及用法

    狀態(tài)機(jī)原理及用法狀態(tài)機(jī)原理及用法狀態(tài)機(jī)原理及用法
    發(fā)表于 03-15 15:25 ?0次下載

    Sniffer用法

    Snffer的定義、分類及基本用法步驟,操作成功后的現(xiàn)象、數(shù)據(jù)包信息
    發(fā)表于 05-30 15:08 ?0次下載

    C語(yǔ)言中#define的一些用法介紹概述

    今天整理了一些#define的用法,與大家共享!1.簡(jiǎn)單的define定義#define MAXTIME 1
    的頭像 發(fā)表于 04-14 11:29 ?7396次閱讀

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

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

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

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

    C語(yǔ)言基礎(chǔ):宏定義使用do{}while(0)的好處

    C語(yǔ)言宏定義使用do{}while(0)的好處1. 概述 經(jīng)常寫(xiě)項(xiàng)目代碼,有時(shí)需要用到宏定義,而宏定義用法是否標(biāo)準(zhǔn),則是會(huì)影響到是否能快速查錯(cuò)以及代碼拓展性的問(wèn)題。在宏
    發(fā)表于 01-13 13:06 ?2次下載
    C語(yǔ)言基礎(chǔ):宏<b class='flag-5'>定義</b>使用do{}while(0)的好處

    ThreadLocal的作用以及應(yīng)用場(chǎng)景

    舉一個(gè)簡(jiǎn)單的例子:目前有100個(gè)學(xué)生等待簽字,但是老師只有一個(gè)筆,那老師只能按順序的分給每個(gè)學(xué)生,等待A學(xué)生簽字完成然后將筆交給B學(xué)生,這就類似Lock,Synchronized的方式。而ThreadLocal是,老師直接拿出一百個(gè)筆給每個(gè)學(xué)生;再效率提高的同事也要付出一個(gè)內(nèi)存消耗;也就是以空間換時(shí)間的概念
    的頭像 發(fā)表于 09-19 10:56 ?1369次閱讀

    ThreadLocal源碼解析及實(shí)戰(zhàn)應(yīng)用

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

    ThreadLocal父子線程之間該如何傳遞數(shù)據(jù)?

    比如會(huì)有以下的這種代碼的實(shí)現(xiàn)。在子線程中調(diào)用 get 時(shí),我們拿到的 Thread 對(duì)象是當(dāng)前子線程對(duì)象,對(duì)吧,每個(gè)線程都有自己獨(dú)立的 ThreadLocal,那么當(dāng)前子線程
    的頭像 發(fā)表于 02-20 11:26 ?927次閱讀

    Java枚舉的特點(diǎn)及用法

    什么是枚舉 Java 枚舉(Enum)是一種特殊的數(shù)據(jù)類型,它是一組預(yù)定義的常量,每個(gè)常量都有一個(gè)名稱和一個(gè)值。 枚舉類型在 Java 中被廣泛使用,它可以用來(lái)代替常量、標(biāo)志位、狀態(tài)碼等,使代碼更加清晰、易讀和易維護(hù)。 2. 枚舉的優(yōu)點(diǎn) 類型安全性:枚舉類型可以
    的頭像 發(fā)表于 09-30 10:02 ?1483次閱讀

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

    的杰作,膜拜一下。怪不得道哥也愛(ài)用,自己設(shè)計(jì)的類總得用用。下面來(lái)看看基本內(nèi)容與用法吧。 2 ThreadLocal原理 “This class provides thread-local
    的頭像 發(fā)表于 10-13 11:39 ?467次閱讀