我对ThreadLocal的一些理解 内存泄露啥的

1. 什么是ThreadLocal

ThreadLocal提供了线程的局部变量,每个线程都可以通过set()和get()来对这个局部变量进行操作,但不会和其他线程的局部变量进行冲突,实现了线程的数据隔离~。

简要言之:往ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的。

ThreadLocal用在什么地方?

讨论ThreadLocal用在什么地方前,我们先明确下,如果仅仅就一个线程,那么都不用谈ThreadLocal的,ThreadLocal是用在多线程的场景的!!!

ThreadLocal归纳下来就2类用途:

  • 保存线程上下文信息,在任意需要的地方可以获取!!!

  • 线程安全的,避免某些情况需要考虑线程安全必须同步带来的性能损失!!!

保存线程上下文信息,在任意需要的地方可以获取!!!

由于ThreadLocal的特性,同一线程在某地方进行设置,在随后的任意地方都可以获取到。从而可以用来保存线程上下文信息。

常用的比如每个请求怎么把一串后续关联起来,就可以用ThreadLocal进行set,在后续的任意需要记录日志的方法里面进行get获取到请求id,从而把整个请求串起来。

还有比如Spring的事务管理,用ThreadLocal存储Connection,ThreadLocal能够实现当前线程的操作都是用同一个Connection,保证了事务!从而各个DAO可以获取同一Connection,可以进行事务回滚,提交等操作。

同样的,Hibernate对Connection的管理也是采用了相同的手法(使用ThreadLocal,当然了Hibernate的实现是更强大的)~

线程安全的,避免某些情况需要考虑线程安全必须同步带来的性能损失!!!

每个线程往ThreadLocal中读写数据是线程隔离,互相之间不会影响的,所以ThreadLocal无法解决共享对象的更新问题!

由于不需要共享信息,自然就不存在竞争问题了,从而保证了某些情况下线程的安全,以及避免了某些情况需要考虑线程安全必须同步带来的性能损失!!!

这类场景阿里规范里面也提到了:

 

ThreadLocal一些细节!

ThreaLocal使用示例代码:

public class ThreadLocalTest {

    private static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();

    public static void main(String[] args{

        new Thread(() -> {

            try {

                for (int i = 0; i < 100; i++) {

                    threadLocal.set(i);

                    System.out.println(Thread.currentThread().getName() + "====" + threadLocal.get());

                    try {

                        Thread.sleep(200);

                    } catch (InterruptedException e) {

                        e.printStackTrace();

                    }

                }

            } finally {

                threadLocal.remove();

            }

        }, "threadLocal1").start();

 

        new Thread(() -> {

            try {

                for (int i = 0; i < 100; i++) {

                    System.out.println(Thread.currentThread().getName() + "====" + threadLocal.get());

                    try {

                        Thread.sleep(200);

                    } catch (InterruptedException e) {

                        e.printStackTrace();

                    }

                }

            } finally {

                threadLocal.remove();

            }

        }, "threadLocal2").start();

    }

}

代码运行结果:

从运行的结果我们可以看到threadLocal1进行set值对threadLocal2并没有任何影响!

Thread、ThreadLocalMap、ThreadLocal总览图:

 

Thread类有属性变量threadLocals (类型是ThreadLocal.ThreadLocalMap),也就是说每个线程有一个自己的ThreadLocalMap ,所以每个线程往这个ThreadLocal中读写隔离的,并且是互相不会影响的。

一个ThreadLocal只能存储一个Object对象,如果需要存储多个Object对象那么就需要多个ThreadLocal!!!

如图:

 

看到上面的几个图,大概思路应该都清晰了,我们Entry的key指向ThreadLocal用虚线表示弱引用 。

2. ThreadLocal实现的原理

首先,我们来看一下ThreadLocal的set()方法,因为我们一般使用都是new完对象,就往里边set对象了

public void set(T value)

{

    // 得到当前线程对象

    Thread t = Thread.currentThread();

 

    // 这里获取ThreadLocalMap

    ThreadLocalMap map = getMap(t);

 

    // 如果map存在,则将当前线程对象t作为key,要存储的对象作为value存到map里面去

    if (map != null)

    map.set(this, value);

    else

    createMap(t, value);

}

 

上面有个ThreadLocalMap,我们去看看这是什么?

static class ThreadLocalMap

{

 

/**

* The entries in this hash map extend WeakReference, using

* its main ref field as the key (which is always a

* ThreadLocal object). Note that null keys (i.e. entry.get()

* == null) mean that the key is no longer referenced, so the

* entry can be expunged from table. Such entries are referred to

* as "stale entries" in the code that follows.

*/

static class Entry extends WeakReference<ThreadLocal<?>> {

    /** The value associated with this ThreadLocal. */

    Object value;

 

    Entry(ThreadLocal<?> k, Object v) {

        super(k);

        value = v;

    }

 }

//....很长

}

 

通过上面我们可以发现的是ThreadLocalMap是ThreadLocal的一个内部类。用Entry类来进行存储

我们的值都是存储到这个Map上的,key是当前ThreadLocal对象

如果该Map不存在,则初始化一个。如果该Map存在,则从Thread中获取

Thread维护了ThreadLocalMap变量 。

可以看出,ThreadLocalMap是在ThreadLocal中使用内部类来编写的,但对象的引用是在Thread中

 

于是我们可以总结出:Thread为每个线程维护了ThreadLocalMap这么一个Map,而ThreadLocalMap的key是LocalThread对象本身,value则是要存储的对象

 

有了上面的基础,我们看get()方法就一点都不难理解了:

public T get()

{

    Thread t = Thread.currentThread();

    ThreadLocalMap map = getMap(t);

    if (map != null) {

        ThreadLocalMap.Entry e = map.getEntry(this);

        if (e != null) {

            @SuppressWarnings("unchecked")

            T result = (T)e.value;

            return result;

        }

    }

    return setInitialValue();

}

ThreadLocal原理总结

(1)每个Thread维护着一个ThreadLocalMap的引用

(2)ThreadLocalMap是ThreadLocal的内部类,用Entry来进行存储

(3)调用ThreadLocal的set()方法时,实际上就是往ThreadLocalMap设置值,key是ThreadLocal对象,值是传递进来的对象

(4)调用ThreadLocal的get()方法时,实际上就是往ThreadLocalMap获取值,key是ThreadLocal对象

(5)ThreadLocal本身并不存储值,它只是作为一个key来让线程从ThreadLocalMap获取value

正因为这个原理,所以ThreadLocal能够实现“数据隔离”,获取当前线程的局部变量值,不受其他线程影响~

3. 避免内存泄露

我们来看一下ThreadLocal的对象关系引用图:

 

 

我们Entry的key指向ThreadLocal虚线表示弱引用。

java对象的引用包括 :强引用,软引用,弱引用,虚引用 。

因为这里涉及到弱引用,简单说明下:

弱引用也是用来描述非必需对象的,当JVM进行垃圾回收时,无论内存是否充足,该对象仅仅被弱引用关联,那么就会被回收。

当仅仅只有ThreadLocalMap中的Entry的key指向ThreadLocal的时候,ThreadLocal会进行回收的!!!

ThreadLocal被垃圾回收后,在ThreadLocalMap里对应的Entry的键值会变成null,但是Entry是强引用,那么Entry里面存储的Object,并没有办法进行回收,所以ThreadLocalMap 做了一些额外的回收工作。

 

ThreadLocal内存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏,而不是因为弱引用

 

想要避免内存泄露就要手动remove()掉

ThreadLocal的最佳实践!

ThreadLocal被垃圾回收后,在ThreadLocalMap里对应的Entry的键值会变成null,但是Entry是强引用,那么Entry里面存储的Object,并没有办法进行回收,所以ThreadLocalMap 做了一些额外的回收工作。

 

备注:很多时候,我们都是用在线程池的场景,程序不停止,线程基本不会销毁!!!

由于线程的生命周期很长,如果我们往ThreadLocal里面set了很大很大的Object对象,虽然set、get等等方法在特定的条件会调用进行额外的清理,但是ThreadLocal被垃圾回收后,在ThreadLocalMap里对应的Entry的键值会变成null,但是后续在也没有操作set、get等方法了。

所以最佳实践,应该在我们不使用的时候,主动调用remove方法进行清理。

 

这里把ThreadLocal定义为static还有一个好处就是,由于ThreadLocal有强引用在,那么在ThreadLocalMap里对应的Entry的键会永远存在,那么执行remove的时候就可以正确进行定位到并且删除!!!

最佳实践做法应该为:

 

抽象为:

try {

    // 其它业务逻辑

finally {

    threadLocal对象.remove();

}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值