ThreadLocal解析

你们有没有经历过这种痛苦:很多方法一层一层调用,每次调用都要传相同的参数。这个时候就可以使用ThreadLocal来解决你的烦恼。

ThreadLocal是什么?

ThreadLocal表示的是线程本地变量,也可以说是每个线程都有同一个变量的独有拷贝。这个听上去可能不是那么好理解,我们先来看一下它能是怎么用的。

ThreadLocal的使用

在ThreadLocal中,我们经常使用的就下面三个方法:

public void set(T value)
public T get()
public void remove()

这三个方法也很简单,见名思意,下面看看它的具体用法

public class ThreadLocalDemo {

    static ThreadLocal<String> threadLocal = new ThreadLocal<>();

    public static void main(String[] args) throws Exception{
        new Thread(()->{
            threadLocal.set("123");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+","+threadLocal.get());
        }).start();
        threadLocal.set("abc");
        Thread.sleep(1000);
        System.out.println(Thread.currentThread().getName()+","+threadLocal.get());
    }

}
// 输出
main,abc
Thread-0,123

用法也很简单,有点类似HashMap,区别在于它的设置和取值都没有用到key。

看到上面这么多,大家可能感觉ThreadLocal也没啥难的,其实,它还是有点东西的。当谈到ThreadLocal,基本都会谈到它的内存泄漏还有它的弱引用。下面我们就来仔细分析一下ThreadLocal原理和它出现内存泄漏的原因。

ThreadLocal原理

首先,我们来看一下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);
}

看上去也很简单,但是这里有几点要说明一下

第一,获取当前线程,然后获取ThreadLocalMap,这个Map就是用来保存我们需要set的值,但是,ThreadLocalMap只是ThreadLocal中的静态内部类,而且获取的ThreadLocalMap并不是定义在ThreadLocal中的属性,而是定义在Thread类中的属性。简单来说:ThreadLocal相当于一个工具类,用它可以获取Thread中的ThreadLocalMap属性,然后将值存入ThreadLocalMap中

第二,在set方法中会调用expungeStaleEntry(int staleSlot)方法,这个方法用来清除key为null的键值对,不仅在set方法中用到,在其他方法如get()和remove()都会用到。为什么这么做呢,这个和后面内存泄漏一起说。

接下来,我们看一下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();
}

这个方法也很简单,获取到ThreadLocalMap,然后通过this,也就是ThreadLocal来获取value。

通过上面两个方法,我们知道,ThreadLocal相当于工具类,用它来操作Thread中的ThreadLocalMap。那这三者是什么关系呢?我们用下面这个图来表示一下:

上图中有一条虚线,它表示弱引用,这就和内存泄漏有关系了

内存泄漏

在提到内存泄漏,很多人会联想到内存溢出,这里先解释一下两者分别是什么意思:

  • 内存泄漏:是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄漏似乎不会有大的影响,但内存泄漏堆积后的后果就是内存溢出。
  • 内存溢出:没内存可以分配给新的对象了。

在ThreadLocal中,为什么会出现内存泄漏呢

上面我们提到,ThreadLocalMap中会存在ThreadLocal的弱引用

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

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

这是ThreadLocalMap中Entry的定义,它继承自弱引用,里面key就是ThreadLocal弱引用。

为什么ThreadLocal要这么设计呢

首先,我们要知道,每个线程都会有一个ThreadLocalMap,如果线程一直存活,那ThreadLocalMap中存的键值对会越来越多。如果将里面key设计成弱引用,当ThreadLocal不存在时,gc会自动回收对应的key,因为ThreadLocal这个工具类都不存在了,那里面存的key和value也自然取不到了,自然可以回收了。这时候gc只会回收key,而对应的value还是存在的,这就有可能造成内存泄漏了。

那value要怎么删除呢

上面我们提到一个方法expungeStaleEntry(int staleSlot),这就是用来处理key为null的value。因此当我们调用set,get和remove方法后,ThreadLocal会自动帮我们删除key为null的value,这就有效降低了内存泄漏的出现。但是,如果gc回收key以后,我们没有使用那些方法,那岂不是还会出现key为null的value?这就需要我们平时开发的时候注意,使用完ThreadLocal后,手动调用remove,保证value也会被回收

总结

ThreadLocal可以用来操作每个线程特有的ThreadLocalMap,用于资源的线程隔离。也可以用作单个线程在各个时间点的资源共享,解决方法之间重复参数的传递。


扫一扫,关注我

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值