0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

ThreadLocal的定义、用法及优点

科技绿洲 来源:Java技术指北 作者:Java技术指北 2023-09-30 10:14 次阅读

ThreadLocal

简介

ThreadLocal是Java中一个非常重要的线程技术。它可以让每个线程都拥有自己的变量副本,避免了线程间的竞争和数据泄露问题。在本文中,我们将详细介绍ThreadLocal的定义、用法及其优点。

ThreadLocal是Java中一个用来实现线程封闭技术的类。它提供了一个本地线程变量,可以在多线程环境下使每个线程都拥有自己的变量副本。每个线程都可以独立地改变自己的副本,而不会影响到其他线程的副本。ThreadLocal的实现是基于ThreadLocalMap的,每个ThreadLocal对象 都对应一个ThreadLocalMap,其中存储了线程本地变量的值。

优缺点

ThreadLocal的主要优点是可以提高并发程序的性能和安全性,同时也存在一些缺点和使用场景需要注意。

优点:

  1. 提高并发性能:使用ThreadLocal可以避免多个线程之间的竞争,从而提高程序的并发性能。
  2. 保证线程安全:每个线程有自己独立的变量副本,避免了线程安全问题。
  3. 简化代码:使用ThreadLocal可以避免传递参数的繁琐,简化代码。

缺点:

  1. 内存泄漏:ThreadLocal变量副本的生命周期与线程的生命周期一样长,如果线程长时间存在,而ThreadLocal变量没有及时清理,就会造成内存泄漏。
  2. 增加资源开销:每个线程都要创建一个独立的变量副本,如果线程数很多,就会增加资源开销。
  3. 不适用于共享变量:ThreadLocal适用于每个线程有独立的变量副本的场景,不适用于共享变量的场景。

适用场景

  1. 线程安全的对象:ThreadLocal适用于需要在多个线程中使用的线程安全对象,例如SimpleDateFormat、Random等。
  2. 跨层传递参数:ThreadLocal可以避免在方法之间传递参数的繁琐,尤其在跨层传递参数的场景中,可以大大简化代码。
  3. 线程局部变量:ThreadLocal可以用于在当前线程中存储和访问局部变量,例如日志、请求信息等。

实现原理

首先通过一张图看下ThreadLocal与线程的关系图:

图片

  1. 每个Thread对象都有一个ThreadLocalMap类型的成员变量threadLocals,这个变量是一个键值对集合,用于存储每个ThreadLocal对象对应的值。
  2. 每个ThreadLocal对象都有一个唯一的ID,用于在ThreadLocalMap中作为键来存储值。
  3. 当一个线程第一次调用ThreadLocal对象的get()方法时,它会先获取当前线程的ThreadLocalMap对象,然后以ThreadLocal对象的ID作为键,从ThreadLocalMap中获取对应的值。
  4. 如果ThreadLocalMap中不存在对应的键值对,则调用ThreadLocal对象的initialValue()方法来初始化一个值,并将其存储到ThreadLocalMap中。
  5. 如果ThreadLocalMap对象的引用不再需要,那么需要手动将其置为null,这样可以避免内存泄漏。

内存泄漏:

ThreadLocal变量副本的生命周期与线程的生命周期一样长,如果线程长时间存在,而ThreadLocal变量没有及时清理,就会造成内存泄漏。为了避免内存泄漏,可以在使用ThreadLocal的地方及时清理ThreadLocal变量,例如在线程池中使用ThreadLocal时,需要在线程结束时手动清理ThreadLocal变量。

内存泄漏出现的原因:

ThreadLocalMap中的Entry对象持有ThreadLocal对象的弱引用,但是ThreadLocalMap中的Entry对象是由ThreadLocal对象强引用的。
如果ThreadLocal对象没有及时清理,在ThreadLocal对象被垃圾回收时,ThreadLocalMap中的Entry对象仍然存在,从而导致内存泄漏。

解决内存泄漏的方法:

在使用ThreadLocal的代码中及时清理ThreadLocal变量。通常情况下,我们可以使用ThreadLocal的remove()方法手动清理ThreadLocal
变量,或者在使用完ThreadLocal变量后将其设置为null

图片

通过上图我们可以看到,在线程方法执行过程中,ThreadLocal、ThreadLocalMap以及Thread之间的引用关系; Thread中存在一个属性threadLocals指向了ThreadLocalMap,ThreadLocal实现线程级别的数据隔离主要是基于该对象;在ThreadLocal中是没有存储任何数据,其更像一个线程与ThreadLocalMap间的协调器,数据存储在ThreadLocalMap中,但是该Map的Key却是ThreadLocal的弱引用;

一般情况下,线程执行完成后,待线程销毁,那么线程对应的属性threadLocals也会被销毁;但是真实环境中对线程的使用大部分都是线程池,这样在整个系统生命周期中, 线程都是有效的,直至线程池关闭。而将ThreadLocalMap的Key设置成弱引用时,经过GC后该Map的Key则变成了null,但是其Value却一直存在,因此需要手动将key为null 的数据进行清理。

下面是一个示例演示如何避免ThreadLocal内存泄漏:

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变量后,调用remove()方法清理ThreadLocal变量
        MyThreadLocal.remove();
    }
}

在上面的代码中,MyThreadLocal类封装了ThreadLocal变量的操作,MyRunnable类实现了Runnable接口,使用MyThreadLocal类来存储和访问 ThreadLocal变量。在MyRunnable的run()方法中,使用完ThreadLocal变量后,调用remove()方法清理ThreadLocal变量,避免了内存泄漏的问题。


ThreadLocal一般会设置成static

主要是为了避免重复创建TSO(thread specific object,即与线程相关的变量。)我们知道,一个ThreadLocal实例对应当前线程中的一个TSO实例。如果把ThreadLocal声明为某个类的实例变量(而不是静态变量),那么每创建一个该类的实例就会导致一个新的TSO实例被创建。而这些被创建的TSO实例是同一个类的实例。同一个线程可能会访问到同一个TSO(指类)的不同实例,这即便不会导致错误,也会导致浪费!

简单的说就是在ThreadLocalMap中,同一个线程是否有必要设置多个ThreadLocal来存储线程变量?

示例

下面是一个简单的例子,演示了如何使用ThreadLocal来实现线程数据隔离:

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());
    }
}

运行结果如下:

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

从输出结果可以看出,每个线程都拥有自己的变量副本,互不影响。而在主线程中,由于没有设置过变量副本,所以返回null。

结束语

ThreadLocal是帮助我们在多个线程间实现线程对数据独享,并不是用来解决线程间的数据共享问题。

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 存储
    +关注

    关注

    13

    文章

    3944

    浏览量

    84953
  • JAVA
    +关注

    关注

    19

    文章

    2916

    浏览量

    103356
  • 程序
    +关注

    关注

    114

    文章

    3663

    浏览量

    79856
  • 多线程技术
    +关注

    关注

    0

    文章

    12

    浏览量

    8522
收藏 人收藏

    评论

    相关推荐

    Stream模块的基础用法和进阶用法

    有用。在本教程中,我们将介绍 Stream 模块的基础用法和进阶用法,并提供示例。 基础用法 在本节中,我们将介绍 Stream 模块的基础用法,并提供基础示例。 从 Vec 中创建
    的头像 发表于 09-19 15:33 827次阅读

    ThreadLocal实例应用

    ThreadLocal相信大家都用过,但你知道他的原理吗,今天了不起带大家学习ThreadLocalThreadLocal是什么 在多线程编程中,经常会遇到需要在不同线程中共享数据的情况
    的头像 发表于 09-30 10:19 516次阅读
    <b class='flag-5'>ThreadLocal</b>实例应用

    用户自定义终止符EOF用法

    EOF(End OF File)在Linux命令和脚本中表示用户自定义终止符,其用法如下:
    发表于 07-23 07:18

    射极跟随器的优点有哪些?

    射极跟随器的优点有哪些? 射极跟随器的定义
    发表于 03-09 17:32 1.7w次阅读

    状态机原理及用法

    状态机原理及用法状态机原理及用法状态机原理及用法
    发表于 03-15 15:25 0次下载

    Sniffer用法

    Snffer的定义、分类及基本用法步骤,操作成功后的现象、数据包信息
    发表于 05-30 15:08 0次下载

    C语言中#define的一些用法介绍概述

    今天整理了一些#define的用法,与大家共享!1.简单的define定义#define MAXTIME 1
    的头像 发表于 04-14 11:29 7140次阅读

    ThreadLocal发生内存泄漏的原因

    前言 ThreadLocal 的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度。但是如果滥用 ThreadLocal
    的头像 发表于 05-05 16:23 3536次阅读

    如何使用ThreadLocal来避免内存泄漏

    本次给大家介绍重要的工具ThreadLocal。讲解内容如下,同时介绍什么场景下发生内存泄漏,如何复现内存泄漏,如何正确使用它来避免内存泄漏。 ThreadLocal是什么?有哪些用途
    的头像 发表于 08-20 09:29 3966次阅读
    如何使用<b class='flag-5'>ThreadLocal</b>来避免内存泄漏

    C语言基础:宏定义使用do{}while(0)的好处

    C语言宏定义使用do{}while(0)的好处1. 概述 经常写项目代码,有时需要用到宏定义,而宏定义用法是否标准,则是会影响到是否能快速查错以及代码拓展性的问题。在宏
    发表于 01-13 13:06 2次下载
    C语言基础:宏<b class='flag-5'>定义</b>使用do{}while(0)的好处

    ThreadLocal的作用以及应用场景

    举一个简单的例子:目前有100个学生等待签字,但是老师只有一个笔,那老师只能按顺序的分给每个学生,等待A学生签字完成然后将笔交给B学生,这就类似Lock,Synchronized的方式。而ThreadLocal是,老师直接拿出一百个笔给每个学生;再效率提高的同事也要付出一个内存消耗;也就是以空间换时间的概念
    的头像 发表于 09-19 10:56 1194次阅读

    ThreadLocal源码解析及实战应用

    ThreadLocal 是一个关于创建线程局部变量的类。
    的头像 发表于 01-29 14:53 353次阅读

    ThreadLocal父子线程之间该如何传递数据?

    比如会有以下的这种代码的实现。在子线程中调用 get 时,我们拿到的 Thread 对象是当前子线程对象,对吧,每个线程都有自己独立的 ThreadLocal,那么当前子线程
    的头像 发表于 02-20 11:26 677次阅读

    c语言宏定义用法规则

    定义会在编译的时候进行替换展开。最好将宏中的参数用括号括起来。这样就避免了当一个表达式同时含有宏定义和其他高优先级运算符时,破坏整个表达式的运算顺序 。
    发表于 07-31 09:39 625次阅读

    ThreadLocal基本内容与用法

    们的杰作,膜拜一下。怪不得道哥也爱用,自己设计的类总得用用。下面来看看基本内容与用法吧。 2 ThreadLocal原理 “This class provides thread-local
    的头像 发表于 10-13 11:39 321次阅读