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

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

3天内不再提示

ThreadLocal实例应用

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

ThreadLocal相信大家都用过,但你知道他的原理吗,今天了不起带大家学习ThreadLocal。

ThreadLocal是什么

在多线程编程中,经常会遇到需要在不同线程中共享数据的情况。通常情况下,为了保证线程安全,我们需要使用锁或其他同步机制。然而,有些情况下,我们希望在每个线程中都有一份独立的数据副本,这就是ThreadLocal派上用场的地方。

ThreadLocal翻译过来就是线程本地,也就是本地线程变量,意思是ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的。ThreadLocal提供了一种机制,允许我们为每个线程创建独立的变量,每个线程都可以独立访问自己的变量,而不会干扰其他线程的数据。ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量,各个线程间互不影响,从而实现线程安全。

ThreadLocal的原理

ThreadLocal的原理涉及到两个重要概念:ThreadLocal实例和ThreadLocalMap。

1. ThreadLocal实例

每个ThreadLocal对象实际上是一个容器,用于存储线程本地的变量副本。每个线程都可以拥有自己的ThreadLocal实例,这些实例可以存储不同的数据,互相之间互不影响。

2. ThreadLocalMap

ThreadLocalMap是ThreadLocal的底层数据结构,它是一个哈希表。每个线程都有一个与之相关联的ThreadLocalMap,用于存储该线程所拥有的ThreadLocal实例以及对应的值。ThreadLocalMap中的键是ThreadLocal实例,值是该线程对应ThreadLocal实例的变量副本。

当我们调用ThreadLocal的set()方法设置值时,实际上是在当前线程的ThreadLocalMap中以ThreadLocal实例为键,将值存储在对应的位置。而调用get()方法时,则是从当前线程的ThreadLocalMap中根据ThreadLocal实例获取对应的值。

ThreadLocal怎么用,应用场景

public static void main(String[] args) {
   IntStream.range(1, 5).forEach(i - > new Thread(() - > {
       // 设置线程中本地变量的值
       threadLocal.set("thread-" + i);
       // 打印当前线程中本地内存中本地变量的值
       System.out.println(threadLocal.get());
       // 清除本地内存中的本地变量
       threadLocal.remove();
       // 打印本地变量
       System.out.println("thread-" + i + " after remove: " + threadLocal.get());
   }).start());
}
/*
thread-1
thread-4
thread-4 after remove: null
thread-2
thread-3
thread-2 after remove: null
thread-1 after remove: null
thread-3 after remove: null
*/

从结果可以看到,每一个线程都有各自的值,并且互不影响。

应用场景

  1. 用户访问之后,在本地线程保存用户的身份信息,在本次访问过程中,可以随时获取用户的身份信息以验证身份。
  2. 在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束。
  3. 数据库连接管理:每个线程可以拥有自己的数据库连接,避免了线程之间的数据库连接混淆。
  4. 用户身份管理:在Web应用中,可以将用户身份信息存储在ThreadLocal中,以便在整个请求处理过程中方便地访问。
  5. 事务管理:将事务状态存储在ThreadLocal中,确保每个线程都能独立管理自己的事务状态。

ThreadLocal源码分析

先看一下 ThreadLocal 和 Thread 的关系

图片

Thread类中有一个threadLocals属性,是ThreadLocal内部类ThreadLocalMap类型的变量,ThreadLocalMap可以看作是一个HashMap,其内部有一个内部类为 Entry,继承了WeakReference>,是一个弱引用。Entry的key是ThreadLocal,value是Object类型的值。

大致了解了Thread和ThreadLocal的关系之后,看一下Thread Local的源码:我们只要看其主要的几个方法,就可以完全了解ThreadLocal的原理了。

set方法

public void set(T value) {
    // 获取当前线程
    Thread t = Thread.currentThread();
    // 通过当前线程获取线程中的ThreadLocal.ThreadLocalMap对象
    ThreadLocalMap map = getMap(t);
    if (map != null)
        // map不为空,则直接赋值
        map.set(this, value);
    else
        // map为空,则创建一个ThreadLocalMap对象
        createMap(t, value);
}
// 根据提供的线程对象,和指定的值,创建一个ThreadLocalMap对象
void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}
// threadLocals是Thread类的一个属性
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

/*
Thread 类 182行
 // ThreadLocal values pertaining to this thread. This map is maintained by the ThreadLocal class.
 与该线程有关的ThreadLocal值。这个映射由ThreadLocal类维护
    ThreadLocal.ThreadLocalMap threadLocals = null;
*/

get方法

// ThreadLocalMap中的内部类,存放key,value
static class Entry extends WeakReference< ThreadLocal< ? >> {
    // 与此ThreadLocal关联的值
    Object value;
 // k:ThreadLocal的引用,被传递给WeakReference的构造方法
    Entry(ThreadLocal< ? > k, Object v) {
        super(k);
        value = v;
    }
}

public T get() {
    // 获取当前线程
    Thread t = Thread.currentThread();
    // 通过当前线程获取线程中的ThreadLocal.ThreadLocalMap对象
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        // map不为空,通过this(当前对象,即ThreadLocal对象)获取Entry对象
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            // Entry不为空,则直接返回Entry中的value值
            return result;
        }
    }
    // 如果map或Entry为空,则返回初始值-null
    return setInitialValue();
}
// 设置初始值,初始化ThreadLocalMap对象,并设置value为 null
private T setInitialValue() {
    // 初始化值,此方法返回 null
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
    return value;
}

remove方法

public void remove() {
    // 通过当前线程获取线程中的ThreadLocal.ThreadLocalMap对象
    ThreadLocalMap m = getMap(Thread.currentThread());
    if (m != null)
        // 移除对象
        m.remove(this);
}
// 根据key,删除对应的所有值
private void remove(ThreadLocal< ? > key) {
    Entry[] tab = table;
    int len = tab.length;
    // 获取key对应的 Entry[] 下标
    int i = key.threadLocalHashCode & (len-1);
    for (Entry e = tab[i];
         e != null;
         // 获取下一个Entry对象
         e = tab[i = nextIndex(i, len)]) {
        if (e.get() == key) {
            e.clear();
            // 通过重新哈希位于staleSlot和下一个null插槽之间的任何可能冲突的条目,来清除陈旧的条目。这还会清除尾随null之前遇到的所有其他过时的条目,防止出现内存泄漏问题
            expungeStaleEntry(i);
            return;
        }
    }
}

总结:

  1. 每个Thread维护着一个ThreadLocalMap的引用
  2. ThreadLocalMap是ThreadLocal的内部类,用Entry来进行存储
  3. ThreadLocal创建的副本是存储在自己的threadLocals中的,也就是自己的ThreadLocalMap。
  4. ThreadLocalMap的键为ThreadLocal对象,而且可以有多个threadLocal变量,因此保存在map中
  5. 在进行get之前,必须先set,否则会报空指针异常,当然也可以初始化一个,但是必须重写initialValue()方法。
  6. ThreadLocal本身并不存储值,它只是作为一个key来让线程从ThreadLocalMap获取value。

注意事项

虽然ThreadLocal在某些情况下非常有用,但过度使用它也可能导致内存泄漏问题。因为ThreadLocalMap中的数据只有在线程结束时才会被释放,如果没有正确地清理ThreadLocal实例,就可能会导致无限制的数据积累。

另外,ThreadLocal不适合在并发量非常大的情况下使用,因为每个线程都会创建自己的变量副本,可能会导致内存消耗过大。

ThreadLocal内存泄漏问题

在上面的代码中,我们可以看出,当前ThreadLocal的引用k被传递给WeakReference的构造函数,所以ThreadLocalMap中的key为ThreadLocal的弱引用。

如果当前线程一直存在且没有调用该ThreadLocal的remove方法,如果这个时候别的地方还有对ThreadLocal的引用,那么当前线程中的ThreadLocalMap中会存在对ThreadLocal变量的引用和value对象的引用,是不会释放的,会造成内存泄漏。

ThreadLocalMap中的Entry的key使用的是ThreadLocal对象的弱引用,在没有其他地方对ThreadLoca依赖,ThreadLocalMap中的ThreadLocal对象就会被回收掉,但是对应的value值不会被回收,这个时候Map中就可能存在key为null但是value不为null的项,也会造成内存泄漏。

小结

ThreadLocal是一种多线程编程的工具,可以帮助我们在多线程环境中管理线程本地的变量。它通过ThreadLocal实例和ThreadLocalMap的组合实现了这一功能。

使用ThreadLocal时需要注意内存泄漏和性能问题,确保合理使用。

使用完ThreadLocal后,一定执行remove操作,避免出现内存泄漏情况。

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

    关注

    8

    文章

    6564

    浏览量

    87951
  • 内存
    +关注

    关注

    8

    文章

    2800

    浏览量

    73119
  • 线程
    +关注

    关注

    0

    文章

    497

    浏览量

    19545
  • 多线程编程
    +关注

    关注

    0

    文章

    15

    浏览量

    6672
收藏 人收藏

    评论

    相关推荐

    ThreadLocal的定义、用法及优点

    ThreadLocal 简介 ThreadLocal是Java中一个非常重要的线程技术。它可以让每个线程都拥有自己的变量副本,避免了线程间的竞争和数据泄露问题。在本文中,我们将详细介绍
    的头像 发表于 09-30 10:14 603次阅读
    <b class='flag-5'>ThreadLocal</b>的定义、用法及优点

    传感器应用实例--数字“表头”电路实例

    传感器应用实例--数字“表头”电路实例
    发表于 12-11 23:15 2次下载

    传感器应用实例--光电转换电路实例

    传感器应用实例--光电转换电路实例
    发表于 12-11 23:03 57次下载

    传感器应用实例--压力测量电路实例

    传感器应用实例--压力测量电路实例
    发表于 12-11 23:03 5次下载

    单片机实例100入门实例知识

    单片机实例100入门实例知识
    发表于 09-21 08:32 38次下载
    单片机<b class='flag-5'>实例</b>100入门<b class='flag-5'>实例</b>知识

    ThreadLocal发生内存泄漏的原因

    ThreadLocal 实现原理 ThreadLocal的实现是这样的:每个Thread 维护一个 ThreadLocalMap 映射表,这个映射表的 key 是 ThreadLocal
    的头像 发表于 05-05 16:23 3536次阅读

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

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

    FastThreadLocal快在哪里

    一个ThreadLocalMap实例变量(如果不使用ThreadLocal,不会创建这个Map,一个线程第一次访问某个ThreadLocal变量时,才会创建)。 该Map是使用线性探测的方式解决hash冲突的问题,如果没有找到空
    的头像 发表于 09-13 09:17 1254次阅读

    Device Studio应用实例之LAMMPS应用实例

    上一期的教程给大家介绍了Device Studio应用实例之Nanodcal应用实例的内容,本期将介绍Device Studio应用实例之LAMMPS应用实例的内容。
    的头像 发表于 07-21 11:23 2996次阅读

    Device Studio应用实例之STEMS应用实例

    上一期的教程给大家介绍了Device Studio应用实例之STEMS应用实例上半部分的内容,本期将介绍Device Studio应用实例之STEMS应用实例下半部分的内容。
    的头像 发表于 07-30 11:06 1663次阅读

    ThreadLocal的作用以及应用场景

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

    ThreadLocal源码解析及实战应用

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

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

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

    什么是ThreadLocal?一文让你彻底掌握ThreadLocal

    我们都知道,在多线程环境下访问同一个共享变量,可能会出现线程安全的问题,为了保证线程安全,我们往往会在访问这个共享变量的时候加锁,以达到同步的效果,如下图所示。
    的头像 发表于 08-07 11:39 1069次阅读
    什么是<b class='flag-5'>ThreadLocal</b>?一文让你彻底掌握<b class='flag-5'>ThreadLocal</b>

    ThreadLocal基本内容与用法

    下面我们就来看看道哥都用的ThreadLocal。 1 ThreadLocal你来自哪里 Since : 1.2 Author : Josh Bloch and Doug Lea 又是并发大佬
    的头像 发表于 10-13 11:39 321次阅读