面试经常问到的ThreadLocal的实现原理

你好,我是风一样的树懒,一个工作十多年的后端开发,曾就职京东、阿里等多家互联网头部企业。

点击下方👇关注公众号,带你一起复习后端技术,看看面试考点,补充积累技术知识,每天都为面试准备积累


由于该模块的内容较多,是面试经常遇到的,而且较长,我们将分成几个模块来学习,也是由于之前最开始的文章太长,大家读起来比较费劲,完读率不是很高,我们进行一定的拆解,慢慢学习。



01
ThreadLocal 的概念


ThreadLocal 是 Java 提供的一种 线程本地变量存储机制,它允许在同一个线程中的不同方法或类之间共享变量,而不需要显式地传递该变量。每个线程都会拥有自己独立的 ThreadLocal 变量副本,其他线程无法访问。

它主要用于解决多线程环境下变量的共享和隔离,避免了使用 synchronized 进行同步,从而提高了性能。


02
ThreadLocal 的实现原理


内部结构

每个线程内部维护一个 ThreadLocalMap,用于存储 ThreadLocal 变量。它的核心结构如下:

public class ThreadLocal<T> {    // 返回当前线程的ThreadLocalMap,并获取变量值    public T get() { ... }    // 设置当前线程的ThreadLocalMap变量值    public void set(T value) { ... }    // 移除当前线程的ThreadLocal变量,避免内存泄漏    public void remove() { ... }}


每个 Thread 类都有一个 ThreadLocalMap,其内部维护了 Key-Value 结构:

  • Key:ThreadLocal 实例(弱引用,防止内存泄漏)

  • Value:存储的实际值


ThreadLocalMap 的实现

(1)Thread 类内部的 ThreadLocalMap

public class Thread {    // 每个线程都有一个独立的ThreadLocalMap    ThreadLocal.ThreadLocalMap threadLocals = null;}


(2)ThreadLocalMap 的数据结构

ThreadLocalMap 类似于 HashMap,但设计上更简单。它是一个 数组结构,数组中的每个元素都是一个 Entry(类似于 HashMap 的 Node):

static class ThreadLocalMap {    static class Entry extends WeakReference<ThreadLocal<?>> {        Object value;        Entry(ThreadLocal<?> k, Object v) {            super(k); // Key是弱引用,防止内存泄漏            value = v;        }    }    private Entry[] table;}


(3)源码

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() 方法

当 ThreadLocal.get() 被调用时:

(1)获取当前线程的 ThreadLocalMap:

ThreadLocalMap map = getMap(Thread.currentThread());


(2)根据 ThreadLocal 的 hashCode() 在 ThreadLocalMap 数组中查找对应的值:

int i = threadLocalHashCode & (table.length - 1);Entry e = table[i];


(3)如果 Entry 存在,则返回 value:

return e.value;

(4)源码

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


set() 方法

当 ThreadLocal.set(value) 被调用时:

(1)获取当前线程的 ThreadLocalMap:

ThreadLocalMap map = getMap(Thread.currentThread());


(2)如果 map 为空,初始化一个 ThreadLocalMap:

if (map == null) {    createMap(Thread.currentThread(), value);}


(3)计算存储索引,并存储 Entry(ThreadLocal, value):

int i = threadLocalHashCode & (table.length - 1);table[i] = new Entry(thisvalue);


remove() 方法

remove() 方法用于 防止内存泄漏,当不再需要 ThreadLocal 变量时,应该手动调用 remove():

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


今天的内容就分享到这儿,喜欢的朋友可以关注,点赞。有什么不足的地方欢迎留言指出,您的关注是我前进的动力!

END


扫码关注

一起积累后端知识
不积跬步,无以至千里
不积小流,无以成江海

喜欢此内容的人还喜欢

《Java面试题指南》回归啦~


一个阿里二面面试官必问的问题


Lambda表达式说爱你不容易


分享面试:mysql数据库索引失效的情况


Spring-Boot中一个不起眼的好工具StopWatch