threadLocal 面试题总结


前言

3月份在面试中总结的java基础多线程ThreadLocal面试题,在这里总结一下


提示:以下是本篇文章正文内容,下面案例可供参考

一、ThreadLocal是什么?

此类提供线程局部变量。 这些变量与普通变量不同,因为每个访问一个线程(通过其get或set方法)的线程都有其自己的,独立初始化的变量副本。 ThreadLocal实例通常是希望将状态与线程关联的类中的私有静态字段(例如,用户ID或事务ID)。

只要线程是活动的并且ThreadLocal实例是可访问的,则每个线程都对其线程局部变量的副本持有隐式引用。 线程消失后,其线程本地实例的所有副本都将进行垃圾回收(除非存在对这些副本的其他引用)

二、Thread,ThreadLocal,ThreadLocalMap这三者是什么关系

1.类之间的关系

然后堆栈里面的

这里解释一下,然后再代码展示

1.首先 ThreadLocalMap 是 Thread 的成员变量,这个属性值为 threadLocals

2.当我们那这个 threadLocal 去调用 get() 方法的时候,拿到当前线程,然后在用当前线程的 threadLocals 这个属性获取threadLocalMap

3.然后拿到这个 threadLocalMap 我们再去 hash 运算,获取这个 Entry ,其中 key 为当前 threadLocal 对象

代码如下(示例):

    public T get() {
        // 获取当前线程
        Thread t = Thread.currentThread();
        
        // 获取该线程下的 threadLocalMap
        ThreadLocalMap map = getMap(t);

        if (map != null) {

            // 通过当前 threadLocal 对象当作 key 获取 Entry 对象
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        
        // 没有则初始化一个
        return setInitialValue();
    }

然后我们再来看threadLocalMap结构

static class ThreadLocalMap {

        // key 为弱引用的 ThreadLocal
        static class Entry extends WeakReference<ThreadLocal<?>> {
            
            Object value;

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

        // table 初始化的容量,2的倍数
        private static final int INITIAL_CAPACITY = 16;

        private Entry[] table;

        //数组中的占用的大小
        private int size = 0;

        // 处罚 resize 的值
        private int threshold; // Default to 0
.....
}

 

2.既然这个Entry的key为一个弱引用的threadLocal,那会有什么问题

关于这个弱应用,当发生gc的时候会将这个弱引用回收掉

当jvm发生gc时,我们的key变为null,相当与下回这个坑位一直被占用,而里面的value为强引用,导致value内存一直占用,产生内存泄漏的问题

 

3.那么作者是怎么解决这个问题的呢

其中,作者在get,set,和remove方法中频繁用了expungeStaleEntry该方法,当便利entry数组的过程中,如果entry不为null,但是key为null,则将 value 置为null,再将这个entry置为null,达到释放对象的目的

代码如下(示例):

        private int expungeStaleEntry(int staleSlot) {
            Entry[] tab = table;
            int len = tab.length;

            // 将该槽位上的置为null
            tab[staleSlot].value = null;
            tab[staleSlot] = null;
            size--;

            Entry e;
            int i;
            for (i = nextIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = nextIndex(i, len)) {
                ThreadLocal<?> k = e.get();
                // entry不为null ,key为null,都将引用去掉
                if (k == null) {
                    e.value = null;
                    tab[i] = null;
                    size--;
                } else {
                    int h = k.threadLocalHashCode & (len - 1);
                    //给当前entry另寻去处
                    if (h != i) {
                        tab[i] = null;
                        while (tab[h] != null)
                            h = nextIndex(h, len);
                        tab[h] = e;
                    }
                }
            }
            return i;
        }

 


总结

面试问的很多都是细节,有些看博客什么的其实是比较大致的,最好还是平时多看源代码,总结一下,思考一下作者为什么这么设计,这么设计有什么好处,那么又有什么缺陷,作者是如何解决的,笔者碰到的面试问题就这些,可能写的不是很详细,望海涵

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值