JDK源码系列:ThreadLocal实现原理

 

        大家好,在软件开发过程中,一般情况下方法之间调用时都是通过接口参数来传递数据的,但有一些公共参数(userId、token、orgId、roleId等)的传递就不能那么干了,在Java中一般用ThreadLocal 去解决这个问题,今天老吕来分析下ThreadLocal的源码。

一、ThreadLocal的本质

通过共享内存来传递数据。但它是如何做到多线程安全的呢?为什么数据就不会串呢?

二、图解ThreadLocal的实现原理

整个过程中参与方主要有这几个角色:

1、Thread

线程,每一个线程都会有一个私有的线程栈空间。

2、ThreadLocal

透明的做到了和线程绑定传递数据,实际上是一个操作 ThreadLocalMap的入口工具。

3、ThreadLocalMap

数组实现的Map。用来存储要传递的数据。

4、Entry

数组元素。

7ab161eae5a82477b14a3a0b6c04514c.png

通过阅读源码,我画出了上面的图,可以看出:

1、要传递的数据实际上并没有在ThreadLocal中存储,而是在ThreadLocalMap中,ThreadLocalMap实际上位于Thread对象中。

上面的注释也说了,这个map被ThreadLocal class 维护。

735716c4b069253102c1a6aee35e7f7a.png

2、ThreadLocal实际上只是一个操作 ThreadLocalMap的入口工具。

3722c969901705ba32661951e10e6a42.png

7aee73c414b00bb95e6bca9084fd7707.png

41730419dc7fe8b67adf1f6eb21dd4ab.png

3、每个线程是如何准确路由到自己的map上的呢?

以当前线程Thread对象来路由找到自己的map的,所以不会有线程安全问题。

ThreadLocalMap map = Thread.currentThread().threadLocals;就这么一句话就路由过去了。

4、内存泄漏指的是哪块内存?怎么就泄漏了呢?

map是由Entry数组实现的,Entry对象的实现如下:可以看到 ThreadLocal对象放入了WeakReference对象里,这就是声明了一个弱引用对象(GC时如果对象只被弱引用引用则会被回收),那是不是有一个疑问,ThreadLocal被GC回收了,这个存储不就完蛋了吗?咋用呢?事实它也不会被轻易回收,因为在你声明的ThreadLocal对象的地方肯定还有一个强引用,只有那个强引用被释放后,再下次GC时,ThreadLocal对象才会被回收。

所以如果你声明的那个ThreadLocal对象强引用被释放了,但是线程并没有结束(考虑线程池情况),这时候就会出现一个现象,Entry对象里key为null,value不为null,但是value永远也用不了了,这就相当于内存泄漏了。

f1d762f9a5218f642d27e71c3ad196cc.png

5、如何避免泄漏?

1)在一个线程每一次运行生命周期的起始端调用 threadLocal.set(o)方法来重置覆盖上次可能遗留的资源

2)在一个线程每一次运行生命周期的末端 调用  threadLocal.remove()方法是否绑定的资源

3)threadLocal一般声明为static类型的对象,避免频繁创建与释放

三、既然明白了ThreadLocal的原理,有没有考虑过自己手写一个工具实现类似功能?

//手撕一个线程绑定传递工具
public class MyMap<K,V> {
     private Map<Thread,Map<K,V>> threadMap = new ConcurrentHashMap<>();


     public void put(K key,V value){
        Map map = threadMap.get(Thread.currentThread());
        if (map==null) {
            threadMap.put(Thread.currentThread(),new HashMap());
        }
        map = threadMap.get(Thread.currentThread());
        map.put(key,value);
     }


     public V get(K key){
        Map<K,V> map = threadMap.get(Thread.currentThread());
        if (map==null) {
            return null;
        }
        return map.get(key);
     }
}




//简单测试一把
public class ThreadLocalClient2 {
    static MyMap<String,String> map = new MyMap<String,String>();
    public static void main(String[] args) {
        ThreadFactory threadFactory = Executors.defaultThreadFactory();
        threadFactory.newThread(new Runnable() {
            @SneakyThrows
            @Override
            public void run() {
                for (int i = 0; i < 5; i++) {
                    map.put("name"+i,Thread.currentThread().getName()+"zhangsan"+i);
                    System.out.println(Thread.currentThread().getName()+":"+map.get("name"+i));
                }
        }
        }).start();


        threadFactory.newThread(new Runnable() {
            @SneakyThrows
            @Override
            public void run() {
                for (int i = 0; i < 5; i++) {
                    map.put("name"+i,Thread.currentThread().getName()+"lishi"+i);
                    System.out.println(Thread.currentThread().getName()+":"+map.get("name"+i));
                }
            }
        }).start();
        while (true);
    }
}


运行结果是正常的
pool-1-thread-1:pool-1-thread-1zhangsan0
pool-1-thread-2:pool-1-thread-2lishi0
pool-1-thread-1:pool-1-thread-1zhangsan1
pool-1-thread-2:pool-1-thread-2lishi1
pool-1-thread-2:pool-1-thread-2lishi2
pool-1-thread-1:pool-1-thread-1zhangsan2
pool-1-thread-1:pool-1-thread-1zhangsan3
pool-1-thread-2:pool-1-thread-2lishi3
pool-1-thread-1:pool-1-thread-1zhangsan4
pool-1-thread-2:pool-1-thread-2lishi4

有没有发现老吕实现的这个线程绑定传递工具类有什么缺陷???

很明显是有内存泄漏问题,线程结束后,map里的数据不能自动释放。

我想这也可能是为什么在ThreadLocal的实现中要将ThreadLocalMap放到Thread对象内部 ,它能随着Thread对象的释放而自动释放,省心。

四、总结

1、每个Thread对象都包含一个 ThreadLocalMap对象,用来存储要传递共享的数据

2、ThreadLocal并不存储数据,它只是提供了操作Thread对象中map对象的操作入口

3、ThreadLocal通过当前线程对象Thread.currentThread()来路由,可以轻松路由到正确的thread对象以及map对象上,解决线程安全问题

4、经常使用ThreadLocal.remove() 方法可以及时释放map中的资源,防止意外情况发生

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

吕哥架构

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值