ThreadLocal源码简析

并发问题最本质的原因其实就是多个线程共享了一个变量,你也要用,我也要用,难免会起冲突
那么很简单的,如果每个人都有一个自己的变量副本,大家各用各的,那不就相安无事了,ThreadLocal就是起的这么一个作用

当一个变量使用了ThreadLocal修饰,那么每个线程都会有一个这个变量副本,操作的也都是这个变量副本,自然不会产生并发安全问题

使用ThreadLocal,其实就是用的他的set和get方法,写个小demo

public class ThreadLocalTest {
    static ThreadLocal<String> tl = new ThreadLocal<>();

    public static void main(String[] args) throws Exception{
        new Thread() {
            @Override
            public void run() {
                tl.set("张三");
                System.out.println(tl.get());
            }
        }.start();

        Thread.sleep(1000);

        new Thread() {
            @Override
            public void run() {
                tl.set("李四");
                System.out.println(tl.get());
            }
        }.start();
    }
}

输出

张三
李四

上面的demo里面两个线程都使用的同一个变量tl,但是打印出的值是不一样的,每个线程都是操作的自己的变量副本,不会影响到其他的线程

这里拿ThreadLocal的set方法来解析一下ThreadLocal的实现原理

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里,map我想大家应该都很熟悉了,ThreadLocal的get方法其实也是根据这个map来获取变量的值的

接下来我们看看这个ThreadLocalMap到底是从哪里获取的

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

看到这里可以发现返回是线程内部的一个threadLocals,继续往下跟一下代码

/* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;

这个ThreadLocalMap其实就是在Thread类的内部,结构如下图所示
ThreadLocal结构
搞了半天,其实ThreadLocal就是将每个变量的变量副本维护在了当前的线程中,各个线程使用自己内部的变量,大家当然就相安无事了,那么问题来了,为什么ThreadLocalMap要维护在Thread内部呢?为什么不维护在ThreadLocal中呢?

首先ThreadLocal只是一个代理工具类,其内部并不存储线程相关的数据,线程相关的数据还是存储在Thread内部,这样设计更加的合理,并且利于理解
其次,如果ThreadLocalMap存储在ThreadLocal中的话,那么如果ThreadLocal对象一直存在,同时ThreadLocalMap中持有着Thread对象的引用,就会造成Thread无法被垃圾回收,最后导致内存泄漏。ThreadLocal又往往存活的比线程要长,所以这个问题就会很容易导致内存泄漏
如果ThreadLocalMap是维护在Thread中的话,ThreadLocalMap里对ThreadLocal的引用是弱引用,那么在Thread被垃圾回收的时候,ThreadLocalMap也就会被回收了,这样可以有效避免内存泄漏

针对ThreadLocalMap的内存泄漏,其实还有一种情况,就是线程池,线程池中的线程有些会存活比较长的时间,所以内部的ThreadLocalMap也就一直不会被回收了,ThreadLocalMap中的Entry对ThreadLocal是弱引用(WeakReference),所以只要ThreadLocal结束了自己 的生命周期是可以被回收掉的。但是Entry中的Value却是被Entry强引用的,所以即便Value的生命周期结束 了,Value也是无法被回收的,从而导致内存泄露,解决方案是使用try{}finally{}来手动释放ThreadLocal,伪代码如下

try{
	// 业务代码
} finally {
	// 手动释放ThreadLocal
	tl.remove();
}

总的来说,对于ThreadLocal的话,只要记住内部是使用了一个ThreadLocalMap的这样一个map来存储变量,同时ThreadLocalMap是在线程自己内部的,这样的好处是可以避免内存泄漏

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值