ThreadLocal源码解析

这篇具有很好参考价值的文章主要介绍了ThreadLocal源码解析。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

使用ThreadLocal可以为每个线程维护一个线程变量,使用场景为线程间隔离,线程内方法共享;

原理:

Thread类中有一个实例属性ThreadLocalMap,ThreadLocalMap中存放的是Entry数组,Entry数组是ThreadLocal和Object的键值对;源码如下--

Thread类:

/*
 * ThreadLocal values pertaining to this thread. This map is maintained
 * by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals;

ThreadLocal->ThreadLocalMap->Entry类:

static class ThreadLocalMap {

    /**
     * The entries in this hash map extend WeakReference, using
     * its main ref field as the key (which is always a
     * ThreadLocal object).  Note that null keys (i.e. entry.get()
     * == null) mean that the key is no longer referenced, so the
     * entry can be expunged from table.  Such entries are referred to
     * as "stale entries" in the code that follows.
     */
    static class Entry extends WeakReference<ThreadLocal<?>> {
        /** The value associated with this ThreadLocal. */
        Object value;

        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }

ThreadLocal有一个内部静态类ThreadLocalMap, ThreadLocalMap是一个Entry数组,通过map方法来访问数据,而Entry对key(ThreadLocal实例对象)的引用使用了虚引用;这里导致key可能会在JVM判断oom之前将key回收掉,导致内存泄漏-此时key对应的ThreadLocal对象被回收变成null,而value存在Thread reference->thread->threadlocalMap->entry-value的强引用链,导致无法回收;虽然在ThreadLocal的get和set以及remove方法中实现了检查是否存在key为null并清理value的机制,但是无法避免内存短时间内泄露的问题;

看一下ThreadLocal的set()和get()方法--

public void set(T value) {
        set(Thread.currentThread(), value);
    }


private void set(Thread t, T value) {
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            map.set(this, value);
        } else {
            createMap(t, value);
        }
    }

ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
public T get() {
        return get(Thread.currentThread());
    }

private T get(Thread t) {
        //获取当前线程的ThreadLocalMap
        ThreadLocalMap map = getMap(t);
        if (map != null) {
  
                ThreadLocalMap.Entry e = map.getEntry(this);
                if (e != null) {
                  
                    T result = (T) e.value;
                    return result;
                }
        }
        return setInitialValue(t);
    }

private Entry getEntry(ThreadLocal<?> key) {
            //这里进行map操作和hashmap的原理基本是一样的,hashcode按位与数组长度减一,数组长度默认为16,(2的n次方减一值做按位与相当于取模,保证了散列均衡)
            int i = key.threadLocalHashCode & (table.length - 1);
            Entry e = table[i];
            if (e != null && e.refersTo(key))
                return e;
            else
                return getEntryAfterMiss(key, i, e);
        }

所以,ThreadLocal维护为每一个线程维护线程变量副本的原理在于,每个线程对象会创建自己的THreadLocalMap--这里并非主动创建,而是调用THreadLocal的set或者get方法的时候才创建;而ThreadLocalMap则是一个以ThreadLocal实例对象的弱应用为key,Object对象为value的键值对数组,以便每个线程对象存放多个线程变量,而对这个线程变量的访问是通过ThreadLocal对象来操作的,所以说实现线程变量的根本不在于ThreadLocal,而是ThreadLocalMap,而ThreadLocal对象只是访问该线程的ThreadLocalMap的一个入口,由于get(Thread t)和set(Thread t,T value)都是私有方法,通过其他对象无法访问,所以只能通过ThreadLocal对象提供的重载方法get()和set(Object value)来访问,保证了线程变量的安全性---其他线程无法修改另外一个线程的ThreadLocalMap;所以核心是getMap(Thread t),而这里的线程对象t只能是Thread.currentThread;

另外需要注意的是,对ThreadLocal 保存的线程变量一定要在使用完之后及时remove掉,一个原因是防止内存泄漏,更重要的原因是在线程池场景下,一个请求可能会读取到上一个请求保存的变量内容,从而造成业务逻辑上的BUG,而且这种BUG还是不易排查的;

另外关于内存泄露的问题JDK提供了一些保障,分为启发式清理和探测式清理--

探测式清理--

原理:

  1. 删除当前元素;
  2. 继续往后遍历,
    1. 当key=null的时候,删除value
    2. 当key!=null的时候,将因哈希碰撞后移的元素重新放置----ThreadLocalMap中使用了后移解决哈希碰撞

源码:

private int expungeStaleEntry(int staleSlot) {
            Entry[] tab = table;
            int len = tab.length;

            // 删除当前元素--value置为null,value引用的对象变成了无引用对象,会被回收;,同样的将数组上该位置的Entry对象置空,原来的Entry对象也会被回收掉;
            tab[staleSlot].value = null;
            tab[staleSlot] = null;
            size--;

            // Rehash until we encounter null
            Entry e;
            int i;
            //继续遍历
            for (i = nextIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = nextIndex(i, len)) {
                ThreadLocal<?> k = e.get();
                //key为空的时候继续删除
                if (k == null) {
                    e.value = null;
                    tab[i] = null;
                    size--;
                } else {//key 不为空的时候rehash,重新计算该key的下标---这里需要注意--rehash的结果可能和原来的值是一样的,因为不能完全保证是否产生过哈希碰撞
                    int h = k.threadLocalHashCode & (len - 1);
                    if (h != i) {
                        tab[i] = null;

                        // Unlike Knuth 6.4 Algorithm R, we must scan until
                        // null because multiple entries could have been stale.
                        while (tab[h] != null)
                            h = nextIndex(h, len);
                        tab[h] = e;
                    }
                }
            }
            return i;
        }

启发式清理--

        往后遍历,如果找到key为null的过期元素, 则调用探测式清理--启发式清理可以理解为为探测式清理找到一个清理入口,由探测式清理完成具体的清理工作

private boolean cleanSomeSlots(int i, int n) {
            boolean removed = false;
            Entry[] tab = table;
            int len = tab.length;
            do {
                i = nextIndex(i, len);
                Entry e = tab[i];
                if (e != null && e.refersTo(null)) {
                    n = len;
                    removed = true;
                    i = expungeStaleEntry(i);
                }
            } while ( (n >>>= 1) != 0);
            return removed;
        }

另外看一下清理工作在哪里被调用--

  1. set(Thread t,T value)->cleabSomeSlots();
  2. set(Thread t,T value)->replaceStaleEntry()->cleanSomeSlots();
  3. get(t)->getEntry()->getEntryAfterMiss()->expungeStaleEntry();
  4. remove()->expungeStaleEntry();
  5. rehash()->expungeStaleEntries->expungeStaleEntry()

上边的调用关系可以看出来基本上我们对ThreadLocal的get,set,remove操作都会触发清理工作,尽量减小了内存泄漏带来的影响---在未做任何操作之前不会主动触发清理,所以在使用ThreadLocal的时候及时调用remove很重要!!!文章来源地址https://www.toymoban.com/news/detail-650947.html

到了这里,关于ThreadLocal源码解析的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 短视频矩阵系统源码开发搭建技术解析-PHP语言

    一、系统架构 整个短视频矩阵系统大概分为以下几个模块: 1.多平台账号管理 支持抖音、快手、小红书、西瓜、头条视频号等多平台账号管理。 2.视频管理模块 支持视频批量上传、批量剪辑、文字转语音,特效等功能配置 3.推荐算法模块 推荐算法模块主要用来推荐用户感兴

    2024年02月07日
    浏览(52)
  • Java语言开发的AI智慧导诊系统源码springboot+redis 3D互联网智导诊系统源码

    Java 语言开发的AI智慧导诊系统源码 springboot+redis 3D 互联网智导诊系统源码 智慧导诊解决盲目就诊问题,减轻分诊工作压力。降低挂错号比例,优化就诊流程,有效提高线上线下医疗机构接诊效率。可通过人体画像选择症状部位,了解对应病症信息和推荐就医科室。 智慧导诊

    2024年04月23日
    浏览(33)
  • ThreadLocal源码

    ThreadLocal是一个线程的本地变量,也就意味着这个变量是线程独有的,是不能与其他线程共享的。这样就可以避免资源竞争带来的多线程的问题。 但是,这种解决多线程安全问题的方式和加锁方式(synchronized、Lock) 是有本质的区别的,区别如下所示: 1 关于资源的管理 当资源

    2024年02月07日
    浏览(32)
  • 26.Netty源码之ThreadLocal

    如果你需要变量在多线程之间隔离,或者在同线程内的类和方法中共享,那么 ThreadLocal 大显身手的时候就到了。ThreadLocal 可以理解为线程本地变量,它是 Java 并发编程中非常重要的一个类。 ThreadLocal 为变量在每个线程中都创建了一个副本,该副本只能被当前线程访问,多线

    2024年02月13日
    浏览(24)
  • Android源码——从Looper看ThreadLocal

    ThreadLocal用于在当前线程中存储数据,由于存储的数据只能在当前线程内使用,所以自然是线程安全的。 Handler体系中,Looper只会存在一个实例,且只在当前线程使用,所以使用ThreadLocal进行存储。 frameworks/base/core/java/android/os/Looper.java Looper类中使用ThreadLocal来存储Looper对象,在

    2024年02月11日
    浏览(22)
  • ThreadLocal的使用介绍和底层原理解析和开源框架的使用实例

    ThreadLocal是一个线程内部的数据存储类,它可以为每个线程提供独立的变量副本,不同线程间的变量无法相互访问和修改。这避免了每个线程都要维护一套独立变量的麻烦,并且也减少了线程之间不必要的数据争用。ThreadLocal适用于这样的场景:每个线程需要有自己单独的实例,而不

    2024年02月02日
    浏览(30)
  • Java源码-servlet源码解析

    Servlet是运行在Web服务器上的Java组件,用于处理客户端请求并生成响应。下面将介绍Servlet的源码解析。 Servlet接口源码解析 Servlet接口是所有Servlet类必须实现的接口。该接口定义了Servlet生命周期方法和服务方法。 init方法初始化Servlet,service方法处理请求并生成响应。destroy方

    2024年02月13日
    浏览(28)
  • java源码-List源码解析

    Java中的List是一个接口,它定义了一组操作列表的方法。List接口的常见子类包括ArrayList、LinkedList和Vector等。 以下是Java中List接口及其常见方法的源码解析: 1. List接口定义 ``` public interface ListE extends CollectionE {     // 返回列表中元素的数量     int size();          // 返回列表

    2024年02月15日
    浏览(24)
  • Java——》ThreadLocal

    推荐链接:     总结——》【Java】     总结——》【Mysql】     总结——》【Redis】     总结——》【Kafka】     总结——》【Spring】     总结——》【SpringBoot】     总结——》【MyBatis、MyBatis-Plus】     总结——》【Linux】     总结——》【MongoDB】    

    2024年02月09日
    浏览(75)
  • java ThreadLocal

    上面三行代码分别是定义、赋值和取值。 介绍: 我们只需要实例化对象一次,并且也不需要知道它是被哪个线程实例化。虽然所有的线程都能访问到这个ThreadLocal实例,但是每个线程却只能访问到自己通过调用ThreadLocal的set()方法设置的值。即使是两个不同的线程在同一个T

    2024年02月14日
    浏览(29)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包