Java的ThreadLocal源码详细解读

 更新时间:2023年08月29日 10:13:10   作者:疯狂的帆  
这篇文章主要介绍了Java的ThreadLocal源码详细解读,ThreadLocal翻译过来就是线程本地,也就是本地线程变量,意思是ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的,需要的朋友可以参考下

ThreadLocal是什么

ThreadLocal翻译过来就是线程本地,也就是本地线程变量,意思是ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的。

Threadlocal 主要用来做线程变量的隔离。

ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量,各个线程间互不影响,从而实现线程安全。

ThreadLocal怎么用

public static void main(String[] args) {
   IntStream.range(1, 5).forEach(i -> new Thread(() -> {
       // 设置线程中本地变量的值
       threadLocal.set("thread-" + i);
       // 打印当前线程中本地内存中本地变量的值
       System.out.println(threadLocal.get());
       // 清除本地内存中的本地变量
       threadLocal.remove();
       // 打印本地变量
       System.out.println("thread-" + i + " after remove: " + threadLocal.get());
   }).start());
}
/*
thread-1
thread-4
thread-4 after remove: null
thread-2
thread-3
thread-2 after remove: null
thread-1 after remove: null
thread-3 after remove: null
*/

从结果可以看到,每一个线程都有各自的值,并且互不影响。

应用场景

  1. 每秒钟同时会有很多用户请求,那每个请求都带有用户信息,我们知道通常都是一个线程处理一个用户请求,我们可以把用户信息丢到Threadlocal里面,让每个线程处理自己的用户信息,线程之间互不干扰。
  2. 在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束。
  3. 线程间数据隔离。
  4. 进行事务操作,用于存储线程事务信息。
  5. 数据库连接,Session会话管理。

ThreadLocal源码分析

thread

先看一下 ThreadLocal 和 Thread 的关系

Thread类中有一个threadLocals属性,是ThreadLocal内部类ThreadLocalMap类型的变量,ThreadLocalMap可以看作是一个HashMap,其内部有一个内部类为 Entry,继承了 WeakReference<ThreadLocal<?>> ,是一个弱引用。Entry的key是 ThreadLocal<?> ,value是Object类型的值。

大致了解了Thread和ThreadLocal的关系之后,看一下Thread Local的源码: 我们只要看其主要的几个方法,就可以完全了解ThreadLocal的原理了。

set方法

public void set(T value) {
    // 获取当前线程
    Thread t = Thread.currentThread();
    // 通过当前线程获取线程中的ThreadLocal.ThreadLocalMap对象
    ThreadLocalMap map = getMap(t);
    if (map != null)
        // map不为空,则直接赋值
        map.set(this, value);
    else
        // map为空,则创建一个ThreadLocalMap对象
        createMap(t, value);
}
// 根据提供的线程对象,和指定的值,创建一个ThreadLocalMap对象
void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}
// threadLocals是Thread类的一个属性
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}
/*
Thread 类 182行
	// ThreadLocal values pertaining to this thread. This map is maintained by the ThreadLocal class.
    ThreadLocal.ThreadLocalMap threadLocals = null;
*/

get方法

// ThreadLocalMap中的内部类,存放key,value
static class Entry extends WeakReference<ThreadLocal<?>> {
    // 与此ThreadLocal关联的值
    Object value;
	// k:ThreadLocal的引用,被传递给WeakReference的构造方法
    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}
public T get() {
    // 获取当前线程
    Thread t = Thread.currentThread();
    // 通过当前线程获取线程中的ThreadLocal.ThreadLocalMap对象
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        // map不为空,通过this(当前对象,即ThreadLocal对象)获取Entry对象
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            // Entry不为空,则直接返回Entry中的value值
            return result;
        }
    }
    // 如果map或Entry为空,则返回初始值-null
    return setInitialValue();
}
// 设置初始值,初始化ThreadLocalMap对象,并设置value为 null
private T setInitialValue() {
    // 初始化值,此方法返回 null
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
    return value;
}

remove方法

public void remove() {
    // 通过当前线程获取线程中的ThreadLocal.ThreadLocalMap对象
    ThreadLocalMap m = getMap(Thread.currentThread());
    if (m != null)
        // 移除对象
        m.remove(this);
}
// 根据key,删除对应的所有值
private void remove(ThreadLocal<?> key) {
    Entry[] tab = table;
    int len = tab.length;
    // 获取key对应的 Entry[] 下标
    int i = key.threadLocalHashCode & (len-1);
    for (Entry e = tab[i];
         e != null;
         // 获取下一个Entry对象
         e = tab[i = nextIndex(i, len)]) {
        if (e.get() == key) {
            e.clear();
            // 通过重新哈希位于staleSlot和下一个null插槽之间的任何可能冲突的条目,来清除陈旧的条目。这还会清除尾随null之前遇到的所有其他过时的条目,防止出现内存泄漏问题
            expungeStaleEntry(i);
            return;
        }
    }
}

总结

  1. 每个Thread维护着一个ThreadLocalMap的引用
  2. ThreadLocalMap是ThreadLocal的内部类,用Entry来进行存储
  3. ThreadLocal创建的副本是存储在自己的threadLocals中的,也就是自己的ThreadLocalMap。
  4. ThreadLocalMap的键为ThreadLocal对象,而且可以有多个threadLocal变量,因此保存在map中
  5. 在进行get之前,必须先set,否则会报空指针异常,当然也可以初始化一个,但是必须重写initialValue()方法。
  6. ThreadLocal本身并不存储值,它只是作为一个key来让线程从ThreadLocalMap获取value。

InheritableThreadLocal

ThreadLocal类:同一个ThreadLocal变量在父线程中被设置值后,在子线程中是获取不到的。

而InheritableThreadLocal类,通过继承ThreadLocal类,并重写了childValue、getMap、createMap三个方法,是可以在子线程中获取到父线程的值。

InheritableThreadLocals类通过重写getMap和createMap两个方法将本地变量保存到了具体线程的inheritableThreadLocals变量中,当线程通过InheritableThreadLocals实例的set或者get方法设置变量的时候,就会创建当前线程的inheritableThreadLocals变量。而父线程创建子线程的时候,ThreadLocalMap中的构造函数会将父线程的inheritableThreadLocals中的变量复制一份到子线程的inheritableThreadLocals变量中。

ThreadLocal内存泄漏问题

简单说一下Java中的4种引用

  • 强引用:不管JVM如何gc,都不会被回收。
  • 软引用:只有JVM内存不足,进行fullGc时,才会被回收。
  • 弱引用:只要出现gc,就会被回收。
  • 虚引用:虚引用是所有引用中最弱的一种引用,其存在就是为了将关联虚引用的对象在被GC掉之后收到一个通知。(不能通过get方法获得其指向的对象)

在上面的代码中,我们可以看出,当前ThreadLocal的引用k被传递给WeakReference的构造函数,所以ThreadLocalMap中的key为ThreadLocal的弱引用。

如果当前线程一直存在且没有调用该ThreadLocal的remove方法,如果这个时候别的地方还有对ThreadLocal的引用,那么当前线程中的ThreadLocalMap中会存在对ThreadLocal变量的引用和value对象的引用,是不会释放的,会造成内存泄漏。

ThreadLocalMap中的Entry的key使用的是ThreadLocal对象的弱引用,在没有其他地方对ThreadLoca依赖,ThreadLocalMap中的ThreadLocal对象就会被回收掉,但是对应的value值不会被回收,这个时候Map中就可能存在key为null但是value不为null的项,也会造成内存泄漏。

使用完ThreadLocal后,一定执行remove操作,避免出现内存泄漏情况。

到此这篇关于Java的ThreadLocal源码详细解读的文章就介绍到这了,更多相关Java的ThreadLocal内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java HashMap算法原理详细讲解

    Java HashMap算法原理详细讲解

    在java开发中,HashMap是最常用、最常见的集合容器类之一,文中通过示例代码介绍HashMap为啥要二次Hash,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习吧
    2023-02-02
  • java实现抽奖概率类

    java实现抽奖概率类

    这篇文章主要为大家详细介绍了java实现抽奖概率类,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-11-11
  • java日期格式化SimpleDateFormat的使用详解

    java日期格式化SimpleDateFormat的使用详解

    这篇文章主要介绍了java SimpleDateFormat使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-05-05
  • Java微信二次开发(一) Java微信请求验证功能

    Java微信二次开发(一) Java微信请求验证功能

    这篇文章主要为大家详细介绍了Java微信二次开发第一篇,Java微信请求验证功能,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-04-04
  • Java 8 Stream流强大的原理

    Java 8 Stream流强大的原理

    这篇文章主要介绍了Java 8 Stream流强大的原理,Java 8 API添加了一个新的抽象称为流Stream,可以让你以一种声明的方式处理数据。本文会对Stream的实现原理进行剖析,具有一定的参考价值,需要的朋友可以参考一下
    2021-12-12
  • Java递归模糊查询文件实例代码

    Java递归模糊查询文件实例代码

    递归算法是一种直接或间接地调用自身的算法,在计算机编写程序中,递归算法对解决一大类问题是十分有效的,它往往使算法的描述简洁而且易于理解,这篇文章主要给大家介绍了关于Java递归模糊查询文件的相关资料,需要的朋友可以参考下
    2021-11-11
  • Java字符串排序的几种实现方式

    Java字符串排序的几种实现方式

    这篇文章主要给大家介绍了关于Java字符串排序的几种实现方式, 使用Java平台进行字符串排序被认为是一件简单的工作,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2023-07-07
  • springboot以FTP方式上传文件到远程服务器

    springboot以FTP方式上传文件到远程服务器

    这篇文章主要介绍了springboot以FTP方式上传文件到远程服务器,需要的朋友可以参考下
    2019-12-12
  • Java中的升序和降序问题

    Java中的升序和降序问题

    这篇文章主要介绍了Java中的升序和降序问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-11-11
  • java基于servlet实现文件上传功能

    java基于servlet实现文件上传功能

    这篇文章主要为大家详细介绍了java基于servlet实现文件上传功能,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-09-09

最新评论