文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Java ThreadLocal类如何使用

2023-07-02 15:06

关注

今天小编给大家分享一下Java ThreadLocal类如何使用的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。

如图所示: 

Java ThreadLocal类如何使用

快速开始

接下来我们就先用一个简单的样例给大家展示一下ThreadLocal的基本用法

package cuit.pymjl.thradlocal;public class MainTest {    static ThreadLocal<String> threadLocal = new ThreadLocal<>();    static void print(String str) {        //打印当前线程中本地内存中本地变量的值        System.out.println(str + " :" + threadLocal.get());        //清除本地内存中的本地变量        threadLocal.remove();    }    public static void main(String[] args) {        Thread t1 = new Thread(new Runnable() {            @Override            public void run() {                //设置线程1中本地变量的值                threadLocal.set("thread1 local variable");                //调用打印方法                print("thread1");                //打印本地变量                System.out.println("after remove : " + threadLocal.get());            }        });        Thread t2 = new Thread(new Runnable() {            @Override            public void run() {                //设置线程1中本地变量的值                threadLocal.set("thread2 local variable");                //调用打印方法                print("thread2");                //打印本地变量                System.out.println("after remove : " + threadLocal.get());            }        });        t1.start();        t2.start();    }}

运行结果如图所示:

Java ThreadLocal类如何使用

ThreadLocal的原理

ThreadLocal相关类图

我们先来看一下ThreadLocal 相关类的类图结构,如图所示: 

Java ThreadLocal类如何使用

 由该图可知, Thread 类中有一个threadLocals 和一个inheritableThreadLocals , 它们都是ThreadLocalMap 类型的变量, 而ThreadLocalMap 是一个定制化的Hashmap 。在默认情况下, 每个线程中的这两个变量都为null ,只有当前线程第一次调用ThreadLocal 的set 或者get 方法时才会创建它们。其实每个线程的本地变量不是存放在ThreadLocal 实例里面,而是存放在调用线程的threadLocals 变量里面。也就是说, ThreadLocal 类型的本地变量存放在具体的线程内存空间中。ThreadLocal 就是一个工具壳,它通过set 方法把value 值放入调用线程的threadLocals 里面并存放起来, 当调用线程调用它的get 方法时,再从当前线程的threadLocals 变量里面将其拿出来使用。 如果调用线程一直不终止, 那么这个本地变量会一直存放在调用线程的threadLocals 变量里面,所以当不需要使用本地变量时可以通过调用ThreadLocal 变量的remove 方法,从当前线程的threadLocals 里面删除该本地变量。另外, Thread 里面的threadLocals 为何被设计为map 结构?很明显是因为每个线程可以关联多个ThreadLocal 变量。 接下来我们来看看ThreadLocal的set、get、以及remove的源码

set

    public void set(T value) {        // 1.获取当前线程(调用者线程)        Thread t = Thread.currentThread();        // 2.以当前线程作为key值,去查找对应的线程变量,找到对应的map        ThreadLocalMap map = getMap(t);        if (map != null) {            // 3.如果map不为null,则直接添加元素            map.set(this, value);        } else {            // 4.否则就先创建map,再添加元素            createMap(t, value);        }    }
    void createMap(Thread t, T firstValue) {                t.threadLocals = new ThreadLocalMap(this, firstValue);    }
    ThreadLocalMap getMap(Thread t) {        //这里就是直接获取调用线程的成员属性threadlocals        return t.threadLocals;    }

get

    public T get() {        // 1.获取当前线程        Thread t = Thread.currentThread();        // 2.获取当前线程的threadlocals,即ThreadLocalMap        ThreadLocalMap map = getMap(t);        // 3.如果map不为null,则直接返回对应的值        if (map != null) {            ThreadLocalMap.Entry e = map.getEntry(this);            if (e != null) {                @SuppressWarnings("unchecked")                T result = (T)e.value;                return result;            }        }        // 4.否则,则进行初始化        return setInitialValue();    }

下面是setInitialValue的代码

private T setInitialValue() {    //初始化属性,其实就是null    T value = initialValue();    //获取当前线程    Thread t = Thread.currentThread();    //通过当前线程获取ThreadLocalMap    ThreadLocalMap map = getMap(t);    //如果map不为null,则直接添加元素    if (map != null) {        map.set(this, value);    } else {        //否则就创建,然后将创建好的map放入当前线程的属性threadlocals        createMap(t, value);    }        //将当前ThreadLocal实例注册进TerminatingThreadLocal类里面    if (this instanceof TerminatingThreadLocal) {        TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);    }    return value;}

这里我需要补充说明一下TerminatingThreadLocal。这个类是jdk11新出的,jdk8中并没有这个类,所以在网上很多源码分析中并未看见这个类的相关说明。 这个类我看了一下源码,其作用应该是避免ThreadLocal内存泄露的问题(感兴趣的可以去看看源码,若有错误,还请指正)。 这是官方对其的解释:

remove

     public void remove() {         //如果当前线程的threadLocals 变量不为空, 则删除当前线程中指定ThreadLocal 实例的本地变量。         ThreadLocalMap m = getMap(Thread.currentThread());         if (m != null) {             m.remove(this);         }     }

小结

在每个线程内部都有一个名为threadLocals 的成员变量, 该变量的类型为Hash Map , 其中key 为我们定义的ThreadLocal 变量的this 引用, value 则为我们使用set 方法设置的值。每个线程的本地变量存放在线程自己的内存变量threadLocals 中,如果当前线程一直不消亡, 那么这些本地变量会一直存在, 所以可能会造成内存溢出, 因此使用完毕后要记得调用ThreadLocal 的remove 方法删除对应线程的threadLocals 中的本地变量。

ThreadLocal内存泄露

为什么会出现内存泄漏?

ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal没有外部强引用来引用它,那么系统 GC 的时候,这个ThreadLocal势必会被回收,这样一来,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value永远无法回收,造成内存泄漏。 其实,ThreadLocalMap的设计中已经考虑到这种情况,也加上了一些防护措施:在ThreadLocal的get(),set(),remove()的时候都会清除线程ThreadLocalMap里所有key为null的value。 但是这些被动的预防措施并不能保证不会内存泄漏:

为什么使用弱引用?

既然我们都知道,使用了弱引用会造成ThreadLocalMap内存泄漏,那么官方为什么依然使用弱引用而不是强引用呢?这就要从使用弱引用和强引用的区别来说起了:

对比可知,使用弱引用至少可以保证不会因为map的key累积从而导致OOM,而对应的value可以通过remove,get,set方法在下一次调用时被清除。可见,内存泄露的根源不是弱引用,而是ThreadLocalMap的生命周期和Thread一样长,造成累积导致的

解决方法

既然问题的根源是value的累积造成OOM,那么我们对症下药,每次使用完ThreadLocal调用remove()方法清理掉就行了。

以上就是“Java ThreadLocal类如何使用”这篇文章的所有内容,感谢各位的阅读!相信大家阅读完这篇文章都有很大的收获,小编每天都会为大家更新不同的知识,如果还想学习更多的知识,请关注编程网行业资讯频道。

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯