硬核剖析ThreadLocal源码,面试官看了直呼内行

工作面试中经常遇到ThreadLocal,但是很多同学并不了解ThreadLocal实现原理,到底为什么会发生内存泄漏也是一知半解?今天一灯带你深入剖析ThreadLocal源码,总结ThreadLocal使用规范,解析ThreadLocal高频面试题 。
1. ThreadLocal是什么ThreadLocal是线程本地变量,就是线程的私有变量,不同线程之间相互隔离 , 无法共享,相当于每个线程拷贝了一份变量的副本 。
目的就是在多线程环境中,无需加锁,也能保证数据的安全性 。
2. ThreadLocal的使用/** * @author 一灯架构 * @apiNote ThreadLocal示例 **/public class ThreadLocalDemo {// 1. 创建ThreadLocalstatic ThreadLocal<String> threadLocal = new ThreadLocal<>();public static void main(String[] args) {// 2. 给ThreadLocal赋值threadLocal.set("关注公众号:一灯架构");// 3. 从ThreadLocal中取值String result = threadLocal.get();System.out.println(result); // 输出 关注公众号:一灯架构// 4. 删除ThreadLocal中的数据threadLocal.remove();System.out.println(threadLocal.get()); // 输出null}}ThreadLocal的用法非常简单 , 创建ThreadLocal的时候指定泛型类型,然后就是赋值、取值、删除值的操作 。
不同线程之间 , ThreadLocal数据是隔离的,测试一下:
/** * @author 一灯架构 * @apiNote ThreadLocal示例 **/public class ThreadLocalDemo {// 1. 创建ThreadLocalstatic ThreadLocal<Integer> threadLocal = new ThreadLocal<>();public static void main(String[] args) {IntStream.range(0, 5).forEach(i -> {// 创建5个线程,分别给threadLocal赋值、取值new Thread(() -> {// 2. 给ThreadLocal赋值threadLocal.set(i);// 3. 从ThreadLocal中取值System.out.println(Thread.currentThread().getName()+ "," + threadLocal.get());}).start();});}}输出结果:
Thread-2,2Thread-4,4Thread-1,1Thread-0,0Thread-3,3可以看出不同线程之间的ThreadLocal数据相互隔离,互不影响 , 这样的实现效果有哪些应用场景呢?
3. ThreadLocal应用场景ThreadLocal的应用场景主要分为两类:

  1. 避免对象在方法之间层层传递,打破层次间约束 。
    比如用户信息,在很多地方都需要用到,层层往下传递,比较麻烦 。这时候就可以把用户信息放到ThreadLocal中 , 需要的地方可以直接使用 。
  2. 拷贝对象副本 , 减少初始化操作,并保证数据安全 。
    比如数据库连接、Spring事务管理、SimpleDataFormat格式化日期,都是使用的ThreadLocal,即避免每个线程都初始化一个对象 , 又保证了多线程下的数据安全 。
使用ThreadLocal保证SimpleDataFormat格式化日期的线程安全,代码类似下面这样:
/** * @author 一灯架构 * @apiNote ThreadLocal示例 **/public class ThreadLocalDemo {// 1. 创建ThreadLocalstatic ThreadLocal<SimpleDateFormat> threadLocal =ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));public static void main(String[] args) {IntStream.range(0, 5).forEach(i -> {// 创建5个线程,分别从threadLocal取出SimpleDateFormat,然后格式化日期new Thread(() -> {try {System.out.println(threadLocal.get().parse("2022-11-11 00:00:00"));} catch (ParseException e) {throw new RuntimeException(e);}}).start();});}}4. ThreadLocal实现原理ThreadLocal底层使用ThreadLocalMap存储数据,而ThreadLocalMap内部是一个数组,数组里面存储的是Entry对象,Entry对象里面使用key-value存储数据,key是ThreadLocal实例对象本身,value是ThreadLocal的泛型对象值 。
硬核剖析ThreadLocal源码,面试官看了直呼内行

文章插图
4.1 ThreadLocalMap源码static class ThreadLocalMap {// Entry对象 , WeakReference是弱引用,当没有引用指向时,会被GC回收static class Entry extends WeakReference<ThreadLocal<?>> {// ThreadLocal泛型对象值Object value;// 构造方法,传参是key-value// key是ThreadLocal对象实例,value是ThreadLocal泛型对象值Entry(ThreadLocal<?> k, Object v) {super(k);value = https://www.huyubaike.com/biancheng/v;}}// Entry数组,用来存储ThreadLocal数据private Entry[] table;// 数组的默认容量大小private static final int INITIAL_CAPACITY = 16;// 扩容的阈值,默认是数组大小的三分之二private int threshold;private void setThreshold(int len) {threshold = len * 2 / 3;}}4.2 set方法源码// 给ThreadLocal设值public void set(T value) {// 获取当前线程对象Thread t = Thread.currentThread();// 获取此线程对象中的ThreadLocalMap对象ThreadLocalMap map = getMap(t);// 如果ThreadLocal已经设过值,直接设值,否则初始化if (map != null)// 设值的key就是当前ThreadLocal对象实例 , value是ThreadLocal泛型对象值map.set(this, value);else// 初始化ThreadLocalMapcreateMap(t, value);}

推荐阅读