前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >面试Threadlocal源码解析

面试Threadlocal源码解析

作者头像
小土豆Yuki
发布2020-06-15 17:35:47
2310
发布2020-06-15 17:35:47
举报
文章被收录于专栏:洁癖是一只狗洁癖是一只狗

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

使用场景

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

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

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

1.initialValue( )

代码语言:javascript
复制
protected T initialValue() {
        return null;
}

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

2.set( )

代码语言:javascript
复制
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的数据结构中。

代码语言:javascript
复制
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发生垃圾回收的时候,自动回收防止内存溢出的情况出现,通过源码就很容易理解

代码语言:javascript
复制
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的实现原理以及内存泄露原因.

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-09-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 洁癖是一只狗 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 使用场景
    • 1.initialValue( )
      • 2.set( )
        • 2.get( )
          • 3.ThreadLocalMap
            • 内存泄露
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档