关于ThreadLoacl的探究(原理,使用场景,内存泄露问题)

解决了什么问题

ThreadLoacl解决了同一线程内多方法共享实例的参数传递问题。也就是如果一个参数在此线程下多个方法中使用时,你可以显示的声明每个方法共用的参数,然后调用的时候进行传递。也可以放到ThreadLoacl中,这样就不用每个方法必须显示的定义共用参数了。因为是同一线程内,所以不涉及多线程的问题。而和多线程相关的就是每个线程有自己的实例,互相之间互不影响。

实现原理

看一下ThreadLoacl的源码能帮助我们更好的理解ThreadLoacl。
当你用ThreadLoacl保存一个线程共用数据时,查看源码发现是保存在了当前线程Thread的成员变量ThreadLocal.ThreadLocalMap threadLocals中。key是你的Threadlocal,value就是你要放的值。
由此可以了解到是如何实现每个线程使用自己的变量副本,是因为数据保存在线程的成员变量中,使用时每个线程使用自己成员变量中保存的数据。

//Threadloacl的set方法
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
//getMap(t); 返回的是线程的成员变量 Thread类内的 ThreadLocal.ThreadLocalMap threadLocals成员变量
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
//ThreadLocal.ThreadLocalMap 主要的内容是一个  Entry,关于map使用Entry保存数据的内容就不再赘述,这里我们需要注意一下 这个Entry继承自WeakReference(弱引用对象)
static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

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

使用场景

既然ThreadLoacl解决了同一线程内多方法共享实例的参数传递问题,那么使用场景就是同一线程中你有一些变量需要在多方法中公用。例如

  1. SimpleDateFormat是线程不安全的,如果你在主线程中创建了这个对象,然后在多个子线程中使用,那么就会存在问题。你就可以放到ThreadLoacl中,这样保证每个线程有自己独立的副本,这样就避免SimpleDateFormat线程不安全的问题。
  2. Spring的@Transactional如何保证同一事物呢,就是使用ThreadLoacl,当你同一线程多个方法涉及到数据库操作时,使用的就是此线程ThreadLoacl中保存的数据库连接从而保证了事物的实现。

内存泄露问题

下面我么来聊一下ThreadLocal存在的内存泄露问题。
首先根据它的原理我们画一个图来帮助我们更加清楚的理解。
内容是 我们在线程创建了一个ThreadLocal对象,然后使用它的副本。
ThreadLoacl的内存使用图形
首先说我们使用完这个数据之后,不在使用时,
1我们通过把当前线程的ThreadLocal引用显示的赋值为null,以方便垃圾回收器在处理时,如果发现对内存中的ThreadLoacl对象没有引用指向时,回收它的内存。
这里我们注意到即便我们把当前线程的ThreadLocal引用显示的赋值为null,但是当前线程对象的ThreadlocalMap中的key依然引用了Threadlocal对象。这样的话我们以为ThreadLocal对象在我们把ThreadLocal引用显示的赋值为null后会被回收,其实却不是,就是因为线程对象的ThreadlocalMap中的key依然引用了Threadlocal对象,这样会造成内存泄漏。那么怎么解决这个问题呢,这里是用的ThreadlocalMap中的key继承WeakReference对象,而针对引用的强、软、弱、虚引用的不同,垃圾回收器的处理方式不同。如果这是一个弱引用,那么垃圾回收器会把引用的对象回收。这样就满足了我们把ThreadLocal引用显示的赋值为null后Threadlocal对象不会被回收的问题。
2当前线程没有结束,那么当Threadlocal对象被回收后,ThreadlocalMap中的value就访问不到了,但是它因为被ThreadlLoaclMap强引用所以依然不会被回收,那么仍然会产生内存泄露。不过Java为了最小化内存泄露的可能性和影响,在ThreadLocal的get、set的时候,都会检查当前key所指的对象是否为null,是则删除对应的value,让它能被GC回收。尽管这样,当我们不在使用这个key时就不会调用他的get和set方法。
所以正确的做法是我们不在使用这个Threadlocal时调用remove方法。

总结下来就是内存泄露涉及到Threadlocal对象的内存泄露和ThreadlocalMap中的value的内存泄露。
当一个线程为不停止的线程和执行时间长的线程时,尤为明显。
正确的做法是我们不在使用这个Threadlocal时调用remove方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值