一文掌握ThreadLocal实现原理【建议收藏】

简介

ThreadLocal是线程本地变量,每个线程私有。

ThreadLocal的主要作用是通过拷贝变量的副本到每个线程中,保证多个线程同时访问变量的数据安全性。

应用场景

ThreadLocal的应用场景:

  • 避免变量在方法之间的传递。如:将用户信息设置到线程的ThreadLocal中,该线程中涉及的方法可以直接通过ThreadLocal获取用户信息,避免用户信息在方法之间的传递。
  • 保证变量的数据安全性。如:将SimpleDataFormat格式化日期变量(共享变量)设置到ThreadLocal中,每个线程访问的是该日期变量的副本,不存在数据安全性问题。

实现原理

ThreadLocal的原理涉及到两个重要概念:ThreadLocal实例和ThreadLocalMap。

  • ThreadLocal实例

每个ThreadLocal对象实际上是一个容器,用于存储线程本地的变量副本。每个线程都可以拥有自己的ThreadLocal实例,这些实例可以存储不同的数据,互相之间互不影响。

  • ThreadLocalMap

ThreadLocalMap是ThreadLocal的底层数据结构,它是一个哈希表。每个线程都有一个与之相关联的ThreadLocalMap,用于存储该线程所拥有的ThreadLocal实例以及对应的值。ThreadLocalMap中的键是ThreadLocal实例,值是该线程对应ThreadLocal实例的变量副本。

当我们调用ThreadLocal的set()方法设置值时,实际上是在当前线程的ThreadLocalMap中以ThreadLocal实例为键,将值存储在对应的位置。而调用get()方法时,则是从当前线程的ThreadLocalMap中根据ThreadLocal实例获取对应的值。

存储结构

线程类Thread中定义了一个threadLocals属性,其类型为
ThreadLocal.ThreadLocalMap,用于存储线程的ThreadLocal变量对应的值,当在线程中通过ThreadLocal变量调用其get()方法或set()方法时,会对线程中的threadLocals变量进行初始化(即:创建ThreadLocalMap对象)。

Thread中的threadLocals属性:

public class Thread implements Runnable {
    // ...省略代码
    ThreadLocal.ThreadLocalMap threadLocals = null;
    // ...省略代码
}

ThreadLocalMap是ThreadLocal的静态内部类,其内部维护了一个Entry类型的数组。

ThreadLocal.ThreadLocalMap中Entry数组相关属性:

// Entry数组初始容量
private static final int INITIAL_CAPACITY = 16;
// Entry数组,用来存储ThreadLocal数据
private Entry[] table;
// Entry数组扩容阀值(默认为初始容量的2/3)
private int threshold;

Entry类是ThreadLocalMap的静态内部类,该类继承了WeakReference(弱引用),并定义了一个Object类型的属性value。

ThreadLocalMap

ThreadLocal.ThreadLocalMap.Entry类:

// Entry对象,WeakReference表示弱引用,当没有引用指向ThreadLocal对象时,ThreadLocal对象会被GC回收
static class Entry extends WeakReference<ThreadLocal<?>> {
    Object value;
    // k:ThreadLocal对象本身,v:Object类型的变量值
    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}

ThreadLocalMap存储结构,如图所示:

ThreadLocalMap存储结构

其中:

  • Entry数组:存储Entry对象。

  • Entry对象:

    • key:ThreadLocal对象本身(弱引用)。
    • value:Object类型的变量值。

hash冲突

ThreadLocalMap中的hash冲突是指在使用ThreadLocalMap存储数据时,不同的ThreadLocal对象可能会产生相同的哈希值,导致数据存储冲突的情况。

ThreadLocalMap采用线性探测法来解决哈希冲突。实现逻辑:

当ThreadLocal对象的哈希值与数组长度取模后的索引位置已经被占用时,会使用线性探测法从冲突位置开始向后查找(环形:遍历到末尾后再从头遍历)未被占用的空槽:

    • 如果在数组中找到空槽,则将键值对存储在该位置上。
    • 如果在数组中没有找到空槽,则会对数组进行扩容操作。

ThreadLocal源码解析

ThreadLocal相当于一个工具类,其核心功能是对ThreadLocalMap进行操作,因此,它对外提供了操作ThreadLocalMap的相关方法:

// 获取变量值
public T get(){}
// 设置变量值
public void set(T value) {}
// 移除变量值
public void remove() {}

获取变量值get方法

获取变量值通过ThreadLocal#get方法实现。

源码解析

ThreadLocal#get方法源码:

// get方法
public T get() {
    // 获取当前线程对象
    Thread t = Thread.currentThread();
    // 根据当前线程获取线程中的ThreadLocalMap对象
    ThreadLocalMap map = getMap(t);
    // ThreadLocalMap对象存在
    if (map != null) {
        // 根据当前ThreadLocal对象获取对应的Entry对象
        ThreadLocalMap.Entry e = map.getEntry(this);
        // Entry对象不为空,则返回变量值
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    // ThreadLocalMap不存在,则初始化ThreadLocalMap对象并根据ThreadLocal对象创建Entry对象(value为null)
    return setInitialValue();
}

// java.lang.ThreadLocal.ThreadLocalMap#getEntry
private Entry getEntry(ThreadLocal<?> key) {
    // 通过ThreadLocal对象的hashcode计算其在Entry数组中的下标位置
    int i = key.threadLocalHashCode & (table.length - 1);
    Entry e = table[i];
    // 如果下标位置对象不为空,并且等于当前ThreadLocal对象,则直接返回Entry对象
    if (e != null && e.get() == key)
        return e;
    else
        // 如果下标位置对象为空或者Entry的key不等于当前ThreadLocal对象,则继续向后遍历Entry数组
        return getEntryAfterMiss(key, i, e);
}

// java.lang.ThreadLocal.ThreadLocalMap#getEntryAfterMiss
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
    Entry[] tab = table;
    int len = tab.length;
    // 循环遍历Entry数组,直到找到ThreadLocal对象,或者遍历到数组为空的位置
    while (e != null) {
        ThreadLocal<?> k = e.get();
        // 如果Entry的key等于当前ThreadLocal对象,表示找到对应的Entry,则直接返回
        if (k == key)
            return e;
        // 如果Entry的key为null,表示ThreadLocal对象已经被GC回收,则清除Entry数组中key为null的Entry
        if (k == null)
            expungeStaleEntry(i);
        else
            // 如果Entry的key不为null(此处key不等于ThreadLocal对象),则索引位置+1,表示继续向后遍历
            i = nextIndex(i, len);
        e = tab[i];
    }
    return null;
}
// java.lang.ThreadLocal.ThreadLocalMap#nextIndex
// 索引位置+1,表示继续向后遍历,遍历到Entry数组结尾,再从头开始遍历
private static int nextIndex(int i, int len) {
    return ((i + 1 < len) ? i + 1 : 0);
}

执行流程

ThreadLocal#get方法执行流程,如图所示:

ThreadLocal#get方法执行流程

设置变量值set方法

设置变量值通过ThreadLocal#set方法实现。

源码解析

ThreadLocal#set方法源码:

public void set(T value) {
    // 获取当前线程对象
    Thread t = Thread.currentThread();
    // 根据当前线程获取线程中的ThreadLocalMap对象
    ThreadLocalMap map = getMap(t);
    // ThreadLocalMap存在
    if (map != null)
        // 以当前ThreadLocal对象为key,变量值为value创建Entry对象加入到ThreadLocalMap中
        map.set(this, value);
    else
        // ThreadLocalMap不存在,则初始化ThreadLocalMap对象
        // 并以当前ThreadLocal对象为key,变量值为value创建Entry对象加入到ThreadLocalMap中
        createMap(t, value);
}
// java.lang.ThreadLocal.ThreadLocalMap#set
private void set(ThreadLocal<?> key, Object value) {
    // 获取ThreadLocalMap中的Entry数组
    Entry[] tab = table;
    // Entry数组长度
    int len = tab.length;
    // 根据当前ThreadLocal对象生成HashCode,与(数组长度-1)取模后得到数组中的下标
    int i = key.threadLocalHashCode & (len-1);
    // 从下标i开始,判断下标位置是否存在对应的Entry对象
    // 如果存在对应的Entry对象且key等于当前ThreadLocal对象,则直接替换变量值,否则继续向后遍历Entry数组,直到找到下标为空的位置(线性探测法)
    for (Entry e = tab[i];
         e != null;
         // 索引位置+1
         e = tab[i = nextIndex(i, len)]) {
        ThreadLocal<?> k = e.get();
        // Entry对象中key不为空且等于ThreadLocal对象,则直接替换变量值
        if (k == key) {
            e.value = value;
            return;
        }
        // Entry对象中key(ThreadLocal对象)为空,说明该Entry中的ThreadLocal对象已被GC回收,则以当前ThreadLocal对象为key,变量值为value设置到过期Entry中
        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }
    // 找到Entry数组中下标为空的位置,以当前ThreadLocal对象为key,变量值为value创建Entry对象加入到ThreadLocalMap中
    tab[i] = new Entry(key, value);
    // 数组元素个数+1
    int sz = ++size;
    // 如果没有需要清除的过期Entry(有清除过期的Entry,肯定不会超过阀值)并且数组中元素个数超过阀值,则进行扩容
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}

其中:

  • threadLocalHashCode:根据每个ThreadLocal对象生成的HashCode。

执行流程

ThreadLocal#set方法执行流程,如图所示:

ThreadLocal#set方法执行流程

移除变量值remove方法

移除变量值通过ThreadLocal#remove方法实现。

源码解析

ThreadLocal#remove方法源码:

public void remove() {
    // 获取当前线程中的ThreadLocalMap对象
    ThreadLocalMap m = getMap(Thread.currentThread());
    // ThreadLocalMap存在
    if (m != null)
        // 移除当前ThreadLocal对象对应的Entry
        m.remove(this);
}

// ThreadLocal.ThreadLocalMap#remove
private void remove(ThreadLocal<?> key) {
    Entry[] tab = table;
    int len = tab.length;
    // 根据当前ThreadLocal对象生成HashCode,与(数组长度-1)取模后得到数组中的下标
    int i = key.threadLocalHashCode & (len-1);
    // 遍历Entry数组,直到找到空位置或者值等于当前ThreadLocal对象才结束
    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {
        if (e.get() == key) {
            // 移除key(当前ThreadLocal对象)
            e.clear();
            // 移除key对应的Entry
            expungeStaleEntry(i);
            return;
        }
    }
}

内存泄漏

Thread|ThreadLocal|ThreadLocalMap引用关系

Thread|ThreadLocal|ThreadLocalMap之间的引用关系,如图所示:

其中:

  • 强引用:强引用是最常见的引用类型,也是默认的引用类型。如果一个对象有强引用与之关联,垃圾回收器就不会回收这个对象,即使内存不足也不会回收强引用的对象。如:Object obj = new Object()中的obj就是一个强引用。
  • 弱引用:弱引用是指使用 WeakReference wf=new WeakRerence<>(obj)或继承WeakReference类。继承WeakReference对象时必须要实现有参构造器,且不能存在无参构造。如果一个对象只有弱引用,无论内存是否充足,只要垃圾回收线程扫描到了弱引用,则会立即对其进行回收。

内存泄漏原因分析

由于ThreadLocalMap是Thread中的一个属性(即:ThreadLocalMap被Thread引用),因此,ThreadLocalMap的生命周期与Thread一致。当ThreadLocalMap中的ThreadLocal变量使用完成(即:不存在对ThreadLocal变量的引用)后,如果没有调用ThreadLocal的remove()方法手动移除ThreadLocal变量对应的Entry,虽然Entry中的key(ThreadLocal对象)被设置成弱引用,会被垃圾回收器回收,但是Entry中的value不会被回收,从而造成内存泄漏。只有当Thread运行结束后,ThreadLocalMap、Entry才会被垃圾回收器回收。

特别是在线程池中使用ThreadLocal时,由于线程池中的线程会被重复使用,未及时清理ThreadLocal对应的Entry不仅会造成内存泄漏,还可能造成业务逻辑处理异常。

内存泄漏解决方案

每次使用完ThreadLocal变量后,调用其remove()方法清理对应的数据。

为何将Entry的key设置为弱引用

将Entry的key设置为弱引用是为ThreadLocal在使用时因未调用其remove()方法清理数据而设计的补救措施,当ThreadLocal变量使用完成(即:不存在对ThreadLocal变量的引用)后,Entry中的key(ThreadLocal对象)因被设计为弱引用会被垃圾回收器回收,Entry中的value则因为是强引用,不会被垃圾回收器回收。但是在调用ThreadLocal的get()、set()方法时,会清除key为null的Entry,从而避免内存泄漏。

使用示例

避免变量在方法之间的传递

/**
 * ThreadLocal使用示例
 */
@Slf4j
public class ThreadLocalExample {

    static class Service1 {
        public void process() {
            UserInfo userInfo = new UserInfo("南秋同学1");
            UserInfoHolder.holder.set(userInfo);
            new Service2().process();
        }
    }

    static class Service2 {
        public void process() {
            UserInfo userInfo = UserInfoHolder.holder.get();
            log.info("Service2中获取用户信息:{}", userInfo.name);
            UserInfoHolder.holder.remove();
            UserInfoHolder.holder.set(new UserInfo("南秋同学2"));
            new Service3().process();
        }
    }

    static class Service3 {
        public void process() {
            UserInfo userInfo = UserInfoHolder.holder.get();
            log.info("Service3中获取用户信息:{}", userInfo.name);
        }
    }

    static class UserInfoHolder {
        public static ThreadLocal<UserInfo> holder = new ThreadLocal<>();
    }

    static class UserInfo {
        String name;
        public UserInfo(String name) {
            this.name = name;
        }
    }

    public static void main(String[] args) {
        new Service1().process();
    }
}

保证变量的数据安全性

/**
 * ThreadLocal使用示例
 */
@Slf4j
public class ThreadLocalExample {
    /**
     * 定义threadLocal,初始化日志格式
     */
    private static ThreadLocal<SimpleDateFormat> sdfThreadLocal
            = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"));

    /**
     * 日志格式转换
     * @param seconds 时间(单位:秒)
     * @return 返回指定日期格式
     */
    public static String dateFormat(int seconds) {
        Date date = new Date(24 * 60 * seconds);
        SimpleDateFormat sdf = sdfThreadLocal.get();
        return sdf.format(date);
    }

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 100; i++) {
            int dateTime = i;
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    String date = dateFormat(dateTime);
                    log.info("打印日期:{}", date);
                }
            });
        }
        executorService.shutdown();
    }
}

InheritableThreadLocal

InheritableThreadLocal继承ThreadLocal,用于实现父子线程之间的变量值传递,使得子线程可以获取父线程设置的变量值。与ThreadLocal一样,使用完InheritableThreadLocal需要调用其remove()方法清除数据,防止发生内存泄漏。

实现原理

InheritableThreadLocal重写了ThreadLocal中的childValue()、getMap()、createMap()方法。InheritableThreadLocal源码:

public class InheritableThreadLocal<T> extends ThreadLocal<T> {
    // 在子线程中初始化父线程的变量值
    protected T childValue(T parentValue) {
        return parentValue;
    }
    // 获取当前线程的inheritableThreadLocals属性(一个ThreadLocalMap对象)
    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }
    // 创建一个ThreadLocalMap对象赋值给当前线程的inheritableThreadLocals属性
    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }
}

线程类Thread中除了定义一个threadLocals属性外,还定义了一个inheritableThreadLocals属性,其类型同样为
ThreadLocal.ThreadLocalMap,用于存储线程的InheritableThreadLocal变量对应的值。

Thread中的inheritableThreadLocals属性:

public class Thread implements Runnable {
    // ...省略代码
    ThreadLocal.ThreadLocalMap threadLocals = null;
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
    // ...省略代码
}

在父线程中创建子线程时,通过Thread构造函数调用其init()方法将父线程的inheritableThreadLocals属性值赋给子线程的inheritablesThreadLocals对象,从而实现父子线程之间的变量值传递。

源码如下:

public Thread() {
    init(null, null, "Thread-" + nextThreadNum(), 0);
}
private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize, AccessControlContext acc,
                  boolean inheritThreadLocals) {
    // ....代码省略
    // 如果线程的inheritThreadLocals为true(默认为true)且父线程的inheritableThreadLocals不为空
    // 则设置子线程的inheritThreadLocals变量为父线程的inheritThreadLocals变量
    if (inheritThreadLocals && parent.inheritableThreadLocals != null)
        this.inheritableThreadLocals =
            ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
    // ....代码省略
}
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
    return new ThreadLocalMap(parentMap);
}
private ThreadLocalMap(ThreadLocalMap parentMap) {
    Entry[] parentTable = parentMap.table;
    int len = parentTable.length;
    setThreshold(len);
    // 初始化子线程的ThreadLocalMap属性
    table = new Entry[len];
    // 遍历父线程inheritableThreadLocals属性中的Entry,依次赋给子线程
    for (int j = 0; j < len; j++) {
        Entry e = parentTable[j];
        if (e != null) {
            @SuppressWarnings("unchecked")
            // 获取父线程inheritableThreadLocals属性中的Entry的key
            ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
            if (key != null) {
                // 通过childValue()方法获取父线程inheritableThreadLocals属性中的Entry的value
                Object value = key.childValue(e.value);
                // 将父线程inheritableThreadLocals属性中Entry的key和value封装成Entry对象添加到子线程的inheritThreadLocals属性中
                Entry c = new Entry(key, value);
                int h = key.threadLocalHashCode & (len - 1);
                while (table[h] != null)
                    h = nextIndex(h, len);
                table[h] = c;
                size++;
            }
        }
    }
}
使用示例
/**
 * InheritableThreadLocals使用示例
 */
@Slf4j
public class InheritableThreadLocalsExample {
    /**
     * 定义父线程的InheritableThreadLocal变量
     */
    private static InheritableThreadLocal<String> inheritableThreadLocals = new InheritableThreadLocal<>();

    public static void main(String[] args) {
        log.info("打印父线程名:{}", Thread.currentThread().getName());
        inheritableThreadLocals.set("南秋同学");
        log.info("获取父线程中的变量值:{}", inheritableThreadLocals.get());
        // 创建子线程
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                log.info("打印子线程名:{}", Thread.currentThread().getName());
                String s = inheritableThreadLocals.get();
                log.info("获取子线程中的变量值:{}", s);
                inheritableThreadLocals.remove();
            }
        });
        thread.start();
    }
}
  • 16
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小小Java开发者

“是一种鼓励,你懂的”

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值