ThreadLocal了解吗?

ThreadLocal了解吗?

1、四种引用类型:强、软、弱、虚引用

  • 强:如果一个对象具有强引用,那么它永远不会被 GC,当内存空间不足时,JVM 宁愿抛出OutOfMemoryError(OOM)
  • 软:用java.lang.ref.SoftReference类来表示。内存充足,垃圾回收器就不会回收它;内存不足,就回收。因此,这一点可以很好地用来解决OOM的问题,并且这个特性很适合用来实现缓存:比如网页缓存、图片缓存等。
  • 弱:用java.lang.ref.WeakReference类来表示,不管当前内存空间足够与否,都会对它进行回收
  • 虚:用java.lang.ref.PhantomReference类表示,不影响对象的生命周期,在任何时候都可能被垃圾回收器回收,永远拿不到虚引用指向的对象
    • ps:虚引用必须和引用队列关联使用虚引用被用来管理JVM中的堆外内存

2、ThreadLocal是什么?

它为线程提供了本地存储,它会为每个线程分别存储一份唯一的数据,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本

3、从数据结构入手

在这里插入图片描述
  • 每个Thread线程内部都有一个Map
  • Map里面存储线程本地对象(key)和线程的变量副本(value)
  • 但是,Thread内部的Map是由ThreadLocal维护的,由ThreadLocal负责向map获取和设置线程的变量值。

4、ThreadLocal底层原理

当一个线程往ThreadLocal里存放数据时

  1. 获取当前线程
  2. 得到当前线程的ThreadLocalMap,如果当前线程的ThreadLocalMap为空,就新建一个
  3. 把ThreadLocal对象当做key,把数据当做value存放到map中

源码:

public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value); //在map中以ThreadLocal对象最为key,值作为value
        else
            createMap(t, value);
    }

void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }


Thread代码中

ThreadLocal.ThreadLocalMap threadLocals = null;

每创建一个线程,线程就有一个ThreadLocalMap(threadLocals)。当一个线程往ThreadLocal里存放数据时,实际上是存到了自己的ThreadLocalMap里,并且在这个map中,以ThreadLocal对象作为key,以要存的值作为value,这就解释了,为什么一个线程往ThreaLocal里存放数据时,其他的线程无法得到数据。

5、ThreadLocalMap

ThreadLocalMap是ThreadLocal的内部类,没有实现Map接口,用独立的方式实现了Map的功能,其内部的Entry也独立实现。

在这里插入图片描述

在ThreadLocalMap中,也是用Entry来保存K-V结构数据的。但是Entry中key只能是ThreadLocal对象,这点被Entry的构造方法已经限定死了。从源码中可以看出,Entry类继承了弱引用类,构造函数中,调用了弱引用类的构造函数,也就是说,Entry对象和ThreadLocal对象之间是弱引用,但只有Key是弱引用类型的,Value并非弱引用。

 static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

ThreadLocalMap的成员变量:

private static final int INITIAL_CAPACITY = 16;

        /**
         * The table, resized as necessary.
         * table.length MUST always be a power of two.
         */
        private Entry[] table;

        /**
         * The number of entries in the table.
         */
        private int size = 0;

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

6、Hash冲突怎么解决

和HashMap的最大的不同在于,ThreadLocalMap结构非常简单,没有next引用,也就是说ThreadLocalMap中解决Hash冲突的方式并非链表的方式,而是采用线性探测的方式,所谓线性探测,就是根据初始key的hashcode值确定元素在table数组中的位置,如果发现这个位置上已经有其他key值的元素被占用,则利用固定的算法寻找一定步长的下个位置,依次判断,直至找到能够存放的位置。

ThreadLocalMap解决Hash冲突的方式就是简单的步长加1或减1,寻找下一个相邻的位置

private static int nextIndex(int i, int len) {
            return ((i + 1 < len) ? i + 1 : 0);
        }

        /**
         * Decrement i modulo len.
         */
private static int prevIndex(int i, int len) {
            return ((i - 1 >= 0) ? i - 1 : len - 1);
        }

显然ThreadLocalMap采用线性探测的方式解决Hash冲突的效率很低,如果有大量不同的ThreadLocal对象放入map中时发送冲突,或者发生二次冲突,则效率很低,如果一个线程要保存多个变量,就需要创建多个ThreadLocal,多个ThreadLocal放入Map中时会极大的增加Hash冲突的可能。

7、ThreadLocal 内存泄漏问题

内存泄漏:对于应用程序来说,当对象已经不再被使用,但是Java的垃圾回收器不能回收它们的时候,就产生了内存泄露。

我们来看下下面这张图:

img

上面这张图详细的揭示了ThreadLocal和Thread以及ThreadLocalMap三者的关系。

  • Thread中有一个map,就是ThreadLocalMap

  • ThreadLocalMap的key是ThreadLocal,值是我们自己设定的。

  • ThreadLocal是一个弱引用,当为null时,会被当成垃圾回收

  • 重点来了,突然我们ThreadLocal是null了,也就是要被垃圾回收器回收了,但是此时我们的ThreadLocalMap生命周期和Thread的一样,它不会回收,这时候就出现了一个现象。那就是ThreadLocalMap的key没了,但是value还在,这就造成了内存泄漏。

    解决办法:使用完ThreadLocal后,执行remove操作,避免出现内存溢出情况。

8、ThreadLocal的使用场景

  • 线程间数据隔离
  • Spring中的声明式事务
  • 数据库连接,Session会话管理,会将Connection对象放到ThreadLocal中,这样一个线程中所有方法使用的都是同一个Connection对象,不同线程使用的是不同的Connection对象。
posted @   码出新生活!  阅读(48)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 【.NET】调用本地 Deepseek 模型
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
· DeepSeek “源神”启动!「GitHub 热点速览」
· 上周热点回顾(2.17-2.23)

阅读目录(Content)

此页目录为空

点击右上角即可分享
微信分享提示