带你掌握被大厂面试官狂轰乱炸的ThreadLocal原理,别再傻乎乎的说不了解了~~~



前言

博主 常年游荡于牛客面经区,总结了字节、阿里、百度、腾讯、美团等等大厂的高频考题,之后会逐步分享给大家,期待各位的关注、点赞!

在这里插入图片描述



什么是ThreadLocal?

ThreadLocal 是一个本地线程副本变量工具类。主要用于将私有线程和该线程存放的副本对象做一个映射,各个线程之间的变量互不干扰



ThreadLocal怎么使用?

ThreadLocl使用比较简单,主要有三个方法:get()、set()、remove()

相信不用我多说各位小伙伴也知道是什么意思

public class Test01 {


    public static void main(String[] args) {

        ThreadLocal<Integer> local = new ThreadLocal<>();

        local.set(10);
        System.out.println(local.get());

        local.remove();
        System.out.println(local.get());

    }
}

在这里插入图片描述



ThreadLocal底层原理


点开 ThreadLocal 的 set()方法

  • 首先是获取到当前线程
  • 调用getMap(t)方法获取到 ThreadLocalMap
  • 如果map不为null则进行set操作,如果为null则进行创建map操作
public void set(T value) {
	
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

点开get()方法

跟HashMap差不多,小伙伴们自己看看源码绝对能懂

public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

private Entry getEntry(ThreadLocal<?> key) {
     int i = key.threadLocalHashCode & (table.length - 1);
     Entry e = table[i];
     if (e != null && e.get() == key)
         return e;
     else
         return getEntryAfterMiss(key, i, e);
 }

点开remove()方法

跟HashMap差不多,找到索引,遍历又对因key就删除

public void remove() {
	   ThreadLocalMap m = getMap(Thread.currentThread());
	   if (m != null)
	       m.remove(this);
}

private void remove(ThreadLocal<?> key) {
	   Entry[] tab = table;
	   int len = tab.length;
	   int i = key.threadLocalHashCode & (len-1);
	   for (Entry e = tab[i];
	        e != null;
	        e = tab[i = nextIndex(i, len)]) {
	       if (e.get() == key) {
	           e.clear();
	           expungeStaleEntry(i);
	           return;
	       }
	   }
}

细心的小伙伴可能就发现了这三个方法中都需要去获取:当前Thread和ThreadLocalMap


那么这是为什么呢???

我们接着看源码,点开getMap()方法

可以发现,ThreadLocalMap获取的是Thread中的一个对象

相信到这大家就明白了 ThreadLocal是怎么做到各个线程互不干扰的把😁

因为获取到的是当前线程的ThreadLocalMap,各个线程所以互不干扰

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

在这里插入图片描述

小伙伴们呢可别觉得已经掌握了,还没完呢,这才哪到哪,😁,我们接着看


打开ThreadLocalMap的set方法

private void set(ThreadLocal<?> key, Object value) {

     Entry[] tab = table;
     int len = tab.length;
     //hash获取对应下标,之后会讲
     int i = key.threadLocalHashCode & (len-1);

     for (Entry e = tab[i];
          e != null;
          e = tab[i = nextIndex(i, len)]) {
         ThreadLocal<?> k = e.get();

         if (k == key) {
             e.value = value;
             return;
         }

         if (k == null) {
             replaceStaleEntry(key, value, i);
             return;
         }
     }
		
	//封装为Entry节点
     tab[i] = new Entry(key, value);
     int sz = ++size;
     if (!cleanSomeSlots(i, sz) && sz >= threshold)
         rehash();
 }

看过上面源码,熟悉HashMap的小伙伴们可能就发现了一个熟悉的身影——>Entry

但这个Entry就跟HashMap的有所不同,它继承了 WeakReference<ThreadLocal<?>>

WeakReference:弱引用

而上面还有段代码不知道小伙伴们注意到没:tab[i] = new Entry(key, value);

这段代码意味着什么相信也不用我多说了吧,就是将key,value封装为 Entry节点;再根据map.set(this, value);这段代码可知,Key为当前的ThreadLocal对象,value为我们要set进来的值

但是这里又有所重点:那就是对key调用了super(k),这里我暂且不多说,涉及到ThreadLocal的一个经典面试题,文章后面会进行详细讲解,小伙伴们要有耐心继续看哦!!!

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

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

到此基本结束,不要奇怪,ThreadLocal其实并不是太难,那我们先来做个总结!



总结

我们set进去的值,最终是放在了当前线程的 ThreadLocalMap 中,并不是存在 ThreadLocal 上,ThreadLocal 可以理解为只是ThreadLocalMap的封装,传递了变量值。 ThrealLocal 类中可以通过Thread.currentThread()获取到当前线程对象后,直接通过getMap(Thread t)可以访问到该线程的ThreadLocalMap对象。

每个Thread中都具备一个ThreadLocalMap,而ThreadLocalMap可以存储以ThreadLocal为 key ,Object 对象为 value 的键值对。



ThreadLocal相关面试题



ThreadLocal 如何解决 Hash 冲突?

与 HashMap 不同,ThreadLocalMap 结构非常简单,没有 next 引用,也就是说 ThreadLocalMap 中解决 Hash 冲突的方式并非链表的方式,而是采用线性探测的方式。所谓线性探测,就是根据初始 key 的 hashcode 值确定元素在 table 数组中的位置,如果发现这个位置上已经被其他的 key 值占用,则利用固定的算法寻找一定步长的下个位置,依次判断,直至找到能够存放的位置。

在这里插入图片描述

使用CAS,每次增加固定的值,所以采用的是线性探测法解决HasH冲突

在这里插入图片描述

经典CAS

不懂CAS的可以看我这篇,写的超详细

public final int getAndAddInt(Object var1, long var2, int var4) {
    int var5;
    do {
        var5 = this.getIntVolatile(var1, var2);
    } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

    return var5;
}


ThreadLocal内存泄漏问题及解决办法

ThreadLocal 在 ThreadLocalMap 中是以一个弱引用身份被 Entry 中的 Key 引用的,因此如果 ThreadLocal 没有外部强引用来引用它,那么 ThreadLocal 会在下次 JVM 垃圾收集时被回收。这个时候 Entry 中的 key 已经被回收,但是 value 又是一强引用不会被垃圾收集器回收,这样 ThreadLocal 的线程如果一直持续运行,value 就一直得不到回收,这样就会发生内存泄露。


在这里插入图片描述

解决办法

1. 使用完后记得remove

2.ThreadLocal自己提供了这个问题的解决方案。

每次操作set、get、remove操作时,会相应调用 ThreadLocalMap 的三个方法,ThreadLocalMap的三个方法在每次被调用时 都会直接或间接调用一个 expungeStaleEntry() 方法,这个方法会将key为null的 Entry 删除,从而避免内存泄漏。


在这里插入图片描述


3. 使用static修饰ThreadLocal

还可以使用 static 修饰ThreadLocal,保证ThreadLocal为强引用,也就能保证任何时候都能通过ThreadLocal的弱引用访问到Entry的value值,进而清除掉 。

原因:根据可达性算法分析,类静态属性引用的对象可作为GC Roots根节点,即保证了ThreadLocal为不可回收对象



最后

我是 CRUD大师,一个热爱分享知识的 皮皮虾爱好者,未来的日子里会不断更新出对大家有益的博文,期待大家的关注!!!

创作不易,如果这篇博文对各位有帮助,希望各位小伙伴可以一键三连哦!,感谢支持,我们下次再见~~~

分享大纲

大厂面试题专栏


Java从入门到入坟学习路线目录索引


开源爬虫实例教程目录索引

更多精彩内容分享,请点击 Hello World (●’◡’●)


在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值