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

文章目录

    • 前言
    • 什么是ThreadLocal?
    • ThreadLocal怎么使用?
    • ThreadLocal底层原理
    • 总结
    • ThreadLocal相关面试题
      • ThreadLocal 如何解决 Hash 冲突?
      • ThreadLocal内存泄漏问题及解决办法
    • 最后



前言

我妻善逸:雷之呼吸

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



什么是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;
}

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

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


打开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>

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 值占用,则利用固定的算法寻找一定步长的下个位置,依次判断,直至找到能够存放的位置。

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

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

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

经典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 就一直得不到回收,这样就会发生内存泄露。


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

解决办法

1. 使用完后记得remove

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

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


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


3. 使用static修饰ThreadLocal

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

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



最后

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

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

分享大纲

大厂面试题专栏


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


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

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


在这里插入图片描述

你可能感兴趣的:(大厂面试题,java,大厂面试题,ThreadLocal)