专栏首页洁癖是一只狗面试Threadlocal源码解析

面试Threadlocal源码解析

今天我们讲一下高频面试题,Threadlocal,他是JDK1.2就已经有了,他是为每一个使用该变量的线程提供独立的副本,可以做到线程间的数据隔离,每一个线程都可以访问各自内部的副本变量。

使用场景

  • 在进行对象跨层传递的时候可以考虑使用ThreadLocal,避免多次传递,打破层次的约束(就是当多个参数传了好几层的时候,而往往参数并不是每一层都需要的参数).
  • 线程间的数据隔离.
  • 进行事物操作,用于存储线程实物信息.

我们先看一下他里面的结构图,看着结构图再看源码,就容易理解多了,

看到Threadlocal的结构图,我们再看一下他常用的几个方法

1.initialValue( )

protected T initialValue() {
        return null;
}

这个方法就是Threadlocal初始化的方法,我们可初始化Threadlocal里面的值,当我们没有set操作的时候,我默认获取的就是这个初始化值,如果你没有重写这个方法返回就是源码中的null.

2.set( )

public void set(T value) {
        Thread t = Thread.currentThread(); //获取当前线程
        ThreadLocalMap map = getMap(t);    //与当前线程绑定的ThreadLocalMap
        if (map != null)                   //如果为null
            map.set(this, value);          //设置当前ThreadLocal为key,设置值value
        else
            createMap(t, value);           //新创建ThreadLocalMap
}

void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);  //新创建ThreadLocalMap
}

private void set(ThreadLocal<?> key, Object value) {
            Entry[] tab = table;        //获取当前Entry数组
            int len = tab.length;       //获取数组的长度
            int i = key.threadLocalHashCode & (len-1);  //根据key的哈希值计算出数组下标

            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {  //循环遍历Entry数组
                ThreadLocal<?> k = e.get();     //获取Entry[i] 的key

                if (k == key) {    //如果k和当前的Thradlocal的key相等
                    e.value = value;  //则覆盖之前的value
                    return;
                }

                if (k == null) {  //如果当前的k为null
                    replaceStaleEntry(key, value, i); //清除为null的位置,使用新的数据占据位置
                    return;
                }
            }

            tab[i] = new Entry(key, value);  //最后也不相等,也不为空,就会重新建立一个Entry
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)//清除key为null的数据,并和阀值比较
                rehash();
}

1.获取当前线程Thread.currentThread(). 2.根据当前线程获取与之关联的ThreadLocalMap数据结构. 3.如果map为null则进入第四步,否则进入第五步. 4.当map为null的时候创建一个ThreadLocalMap,用当前ThreadLocal实例作为key,将要存放的数据作为value,对应到ThreadLocalMap中则是创建了一个Entry 5.在map的set方法中遍历整个map的Entry,如果发现ThreadLocal相同,则替换成新的数据,set结束 6.在遍历的过程中,发现Entry中key为null,则会直接将其清除,并且使用新的的数据占用被逐出数据的位置,主要是防止内存泄露 7.创建新的entry,使用ThreadLocal作为Key,将要存放的数据作为value. 8.最后在根据ThreadlocalMap的大小和阀值比较,并再清理key为null的数据.

2.get( )

get用于返回当前线程在Threadlocal中的数据备份,当前线程的数据放在一个ThreadLoclaMap的数据结构中。

public T get() {
        Thread t = Thread.currentThread(); //获取当前线程
        ThreadLocalMap map = getMap(t); //获取当前线程绑定的ThreadLocalMap
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);//获取ThreadLocalMap的entry
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;  //返回ThreadLocalMap的entry的value
            }
        }
        return setInitialValue();//初始化设置值
}

 private T setInitialValue() {
        T value = initialValue();  //初始化值
        Thread t = Thread.currentThread(); //获取当前线程
        ThreadLocalMap map = getMap(t); //当前线程绑定的ThreadLocalMap
        if (map != null)
            map.set(this, value);//把当前的ThreadLocal设置为ThreadLocalMap的key,当前数据设置成value
        else
            createMap(t, value);//创建新的ThreadLocalMap
        return value;//返回初始化的值,如果没有重写initialValue,默认返回null
 }

1.首先获取当前线程Thread,currentThread()方法 2.根据Thread获取ThreadlocalMap,其中ThreadLocalMap与Thread关联,而我们存入的Threadlcoal中的数据实时上是存储在ThreadlocalMap的Entry中的 3.如果map已经被创建过,则以当前的Threadlocal作为key获取对应的Entry. 4.如果Entry不为null,则直接返回Entry的value的值,否则进入第五步 5.如果在第二步获取不到对应的ThreadLocalMap,则执行setInitialValue()方法. 6.在sentInitialValue()方法中首先通过执行initialValue()所获得的初始值, 7.根据当前线程Thread获取对应的ThreadlocalMap. 8.如果ThreadLocalMap不为null,则为map指定initalValue()所获得的初始值,实际上是在map.set(this,value)方法中new了一个新的Entry对象. 9.如果ThreadLocalMap为null(首次使用的时候),则创建一个ThreadLocalMap,并且与Thread对象的threadlocals属性相关联(这里发现ThreadLocalMap构造过程是一个Lazy的方式) 10,返回initialValue()重写的结果,当然这个结果在没有重写的时候,结果为null.

3.ThreadLocalMap

ThradLocalMap是一个类似于HashMap的数据结构,用于把线程放在ThreadLocal中的数据进行备份,ThreadLocalMap所有方法对外不可见,注意一点ThreadLocalMap中的存储数据的Entry,他是一个WeakReference类型的子类,之所以设计成WeakReference是为了能够在JVM发生垃圾回收的时候,自动回收防止内存溢出的情况出现,通过源码就很容易理解

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

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

内存泄露

我们先看一张图

对于ThreadLocal内存泄露,网上有很多说法,都说的很对,但是个人理解,有些说的有些模糊.

  • 为什么要把ThreadLockMap的key(这个key就是ThreadLocal对象)搞成弱引用? 我们看上面的图,如果key不是弱引用,当我们把threadlocal设置为null的时候,但是ThreadLocalMap一直存在强引用,如果线程不结束,那么他就一直不会被回收,就可能就会引起内存泄露。
  • 那key已经是弱引用了,为什么还会导致内存泄露呢? 我们知道key为弱引用之后,Threadlocal设置为null是就可以被回收了,但是entry的value值不为null,因此就一直不会被释放,所以还是有可能引起内存泄露.
  • ThreadLocal源码不是在set get 等方法不是有清除机制吗,为什么还需要我们去remove? 看过源码的看知道,set,get仅仅在一定条件才会清除key为null的entry,因此也有可能引起内存泄露,所以建议我们使用的时候,进行手动remove.

到此我们讲解完了ThreadLocal的实现原理以及内存泄露原因.

本文分享自微信公众号 - 洁癖是一只狗(rookie-dog),作者:洁癖汪

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-09-02

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 雷锋教你阿里面试题

    看到结果。我们的结论完全正确,有的面试官也可能问一下loadClass在什么时候执行静态代码块呢,其实当执行newInstance()时候才会执行静态代码块。

    小土豆Yuki
  • Python流程控制语句详细解读 含代码

    今天我们详细的讲讲Python流程控制语句。包括if条件判断,while循环以及break和continue等。下一篇我们主讲Python中的序列,包括列...

    小土豆Yuki
  • Zookeeper基础篇---面试Leader选举

    当集群已经有过半的Follower完成同步Leader的状态,整个集群zk就进入了消息广播模式。

    小土豆Yuki
  • 「周末福报」头铁的我,一头扎进了知识盲区 ThreadLocal

    今天,在思考如何多线程场景下 Kryo 数据安全问题的时候。扎进了知识盲区 ThreadLocal。「老脸一红

    FoamValue
  • ThreadLocal用法及原理

    Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离。

    烂猪皮
  • 为了不让GPU等CPU,谷歌提出“数据回波”榨干GPU空闲时间,训练速度提升3倍多

    因为通用计算芯片不能满足神经网络运算需求,越来越多的人转而使用GPU和TPU这类专用硬件加速器,加快神经网络训练的速度。

    量子位
  • VIPKID估值超过30亿美金?创始人身价暴涨,腾讯成为大赢家

    熟悉在线英语教育的朋友,一定听说过VIPKID这个平台。根据媒体的报道,VIPKID目前已经是全球最大的少儿英语在线教育平台之一。从去年8月到今年7月,不到一年...

    光荣与梦想1987
  • Java HashMap的工作原理

    面试的时候经常会遇见诸如:“java中的HashMap是怎么工作的”,“HashMap的get和put内部的工作原理”这样的问题。本文将用一个简单的例子来解释下...

    Java团长
  • Python-科学计算-pandas-12-df单列计算

    系统:Windows 7 语言版本:Anaconda3-4.3.0.1-Windows-x86_64 编辑器:pycharm-community-2016.3....

    zishendianxia
  • Arduino如何同时使用多个串口

    如果想要给Arduino UNO R3同时接上WiFi模块和蓝牙模块时,但是Arduino的串口只有一个,怎样才能让Arduino同时使用多个串口呢?

    小雨编程

扫码关注云+社区

领取腾讯云代金券