常见面试题梳理:源码角度彻底揭秘ThreadLocal

深入剖析ThreadLocal:内存泄漏、源码解析与Spring应用
本文详细解读ThreadLocal的工作原理,包括底层实现、内存泄漏机制,以及在Spring中如何确保事务原子性。特别关注setInitialValue和remove方法,以及如何避免内存泄漏的最佳实践。

序言

ThreadLocal在日常开发中还是比较常见的,本文将从源码的角度彻底揭秘ThreadLocal,并会分享一些较为常见的面试题,let's go。

ThreadLocal是什么?

ThreadLocal隶属于lang包,它的主要功能是为每个线程提供一个私有的局部变量,这个变量在线程间相互隔离,互不影响。

主要解决的就是单例情况下全局变量的线程安全问题

ThreadLocal的底层实现

set方法

public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
复制代码
  • 通过set方法可得知,先获取到当前的Thread对象,然后调用getMap(t)方法
ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
 }
复制代码
  • getMap方法的内部实现也很简单,直接调用t的threadlocals字段,来获取到当前线程对应的ThreadLocalMap对象
  • 接下来会判断map是否存在,不存在的话就去创建出map,存在的话就调用map.set(this,value)方法了
private void set(ThreadLocal<?> key, Object value) {
            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);

            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();

                if (k == key) {
                    e.value = value;
                    return;
                }

                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }

            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
     }
复制代码
  • 以当前的ThreadLocal作为key,set的值作为value,然后封装成entry对象放到ThreadLocalMap当中。

当发生hash冲突时,采用的解决方式是线性探测法来解决的。

set方法小总结

通过set方法的阅读,我们基本可以得出以下结论:ThreadLocal本身不存放数据,而是通过Thread对应的ThreadLocalMap来存放数据,ThreadLocal只是作为key。

ThreadLocalMap

static class ThreadLocalMap {
        static class Entry extends WeakReference<ThreadLocal<?>> {
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
}
复制代码
  • ThreadLocalMap中通过一个内部类Entry来存放key,value,我们需要注意的是这个Entry对象是继承自WeakReference,WeakReference对象是一个弱引用对象(弱引用对象的特点是:当垃圾收集器进行gc时,如果没有引用指向弱引用对象的话,那么就会进行回收)

弱引用仅限于Key,value还是强引用对象

为什么key要设为弱引用?

我个人认为,key设为弱引用,是为了方便当ThreadLocal对象使用完毕后将key进行垃圾回收,避免出现内存泄漏。

key是不内存泄漏了,但是value还是会出现内存泄漏。(过会儿我们仔细说一下value的内存泄漏问题)

get方法

public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }
复制代码
  • get方法的主要作用就是获取我们set进去的值,先获取到threadLocalMap对象,然后将当前的threadLocal对象引用作为key从threadLocalMap中获取到对应的value。

从源码中可知,我们需要注意的是,有可能在操作threadLocal对象时,没有先执行set()方法,直接调用get()方法,那么它会返回setInitialValue()方法,我们一起来看看setInitialValue()做了什么。

private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }
复制代码
  • 第一行调用了initialValue()方法,我们先按下表,看看initialValue()做了什么
protected T initialValue() {
        return null;
    }
复制代码

initialValue()方法直接返回了null

  • 回到setInitialValue()方法,我们可以知道第一行代码T value = initialValue()执行完后,value是null,接下来的代码相信大家已经不陌生了
  • 继续获取到当前Thread对象,然后获取到ThreadLocalMap对象,然后以ThreadLocal对象作为key,null作为value写入到ThreadLocalMap当中。
  • 然后返回null

get方法小总结

会以当前的ThreadLocal作为key,从ThreadLocalMap中获取到set的value。

如果我们没有调用set,而直接调用get的话,默认情况下会返回null(并帮我们调用set方法,value就设为null)

initialValue方法

从上面我们可以知道,默认情况下initialValue方法是返回null的,其实我们在新建ThreadLocal对象时可以重写initialValue方法。

ThreadLocal<String> threadLocal=new ThreadLocal<String>(){
           @Override
           protected String initialValue() {
              return "haha";
          }
   };
  System.out.println(threadLocal.get());
复制代码

我们重写了initialValue方法,这样在直接get时就会获取到我们写的“haha”了,运行结果如下:

常见面试题梳理:源码角度彻底揭秘ThreadLocal

 

remove方法

public void remove() {
    ThreadLocalMap m = getMap(Thread.currentThread());
    if (m != null)
         m.remove(this);
  }
  private void remove(ThreadLocal<?> key) {
      Entry[] tab = table;
      int len = tab.length;
      int i = key.threadLocalHashCode & (len-1);
       for (Entry e = tab[i];
             e != null;
             e = tab[i = nextIndex(i, len)]) {
             if (e.get() == key) {
                e.clear();
                expungeStaleEntry(i);
                return;
             }
        }
  }
复制代码

remove方法一般在用完ThreadLocal后进行调用,它的主要作用就是清除掉当前ThreadLocal对象在ThreadLocalMap中对应的entry。

ThreadLocal底层图

常见面试题梳理:源码角度彻底揭秘ThreadLocal

 

ThreadLocal的内存泄漏问题

内存泄漏:内存泄漏就是指我们使用完毕的资源,没有得到及时的释放,jvm还认为该资源有用,不会对其进行回收,导致该资源一直占用着我们的内存,最终很有可能导致内存溢出。

ThreadLocal的内存泄漏:上面我们提到ThreadLocalMap的key设为弱引用是为了解决key内存泄漏的问题,但value依旧是会有内存泄漏问题存在的。

当我们使用完ThreadLocal后,垃圾回收器会将key给回收掉,但是value却是一直存在的,直到线程结束才会释放,但我们日常开发中会有使用线程池的场景,在这个场景下线程的生命周期都是较长的,这个时间段内就造成了value的内存泄漏,因此ThreadLocal的内存泄漏和key是不是弱引用关系不大,主要还是由于使用完后没有调用remove()方法造成的。

为了避免内存泄漏,我们最好在使用完ThreadLocal后,调用其remove()方法。

ThreadLocal在Spring中的应用

Spring框架相信大家都不陌生,Spring框架中有一个@Transactional注解,它是用于保证事务的。

事务的主要作用就是保证同一事务下的操作要么全部成功,要么全部失败,但有一个前提条件就是这些操作必须使用同一个数据库连接,但是数据库连接不是线程安全的,它在多线程环境下会出现问题。

Spring为了保证事务的原子性,它就采用了ThreadLocal这个数据结构,用ThreadLocal来保存连接,set的类型是一个Map,key是数据源、value是连接,定义成map是为了应对多数据源的场景的,当采用了ThreadLocal后也就可以保证了我们同一线程在事务内的所有操作获取到的连接是同一个连接,也就保证了事务的原子性了。

原文链接:
https://juejin.cn/post/6931159589203214350

如果觉得本文对你有帮助,可以转发关注支持一下

在数字化环境中,线上票务获取已成为参与各类活动的主要途径。随着公众对热门演出需求的增长,票源往往在开放销售后迅速告罄,导致普通消费者难以顺利购得所需票券。为应对这一挑战,部分技术开发者借助编程手段构建了自动化购票辅助程序,旨在提升用户成功获取门票的概率。本文将以一个针对特定票务平台设计的自动化工具为例,系统阐述其设计理念、技术组成及具体实施流程。 秀动网作为国内知名的演出及体育赛事票务销售平台,因活动热度较高,常出现访问拥堵、瞬时抢购压力大等现象,使得常规购票过程面临困难。因此,开发一款能够协助用户更有效完成票务申购的辅助工具具有实际意义。 该工具主要具备以下几项关键功能:持续监控目标平台的票务信息更新;在票务释放时自动执行选座、添加至购物车及提交订单等系列操作;集成一定的异常处理机制,以应对网络延迟或服务器响应异常等情况。 在技术实现层面,选用Python作为开发语言,主要基于其语法简洁、标准库与第三方资源丰富,适合快速构建功能原型。同时,Python在网络通信与浏览器自动化方面拥有如requests、selenium等成熟支持库,为程序实现网页交互与数据抓取提供了便利。 开发过程主要包括以下环节:首先解析目标网站的页面结构,明确可通过程序操控的网页元素路径;随后编写监控模块,实时检测新票务信息的上线并及时触发后续操作;接着模拟用户操作流程,包括自动填写个人信息、选择座位偏好、完成购物车添加等步骤,并通过行为模拟降低被平台反爬虫机制识别的可能;最终实现订单自动提交,并在成功购票后向用户发送通知。 此外,该工具提供了可配置的操作界面,允许用户根据个人需求设定抢票时间、目标活动类型及座位选择等参数,从而在提升使用体验的同时,减少对票务平台服务器资源的非必要占用。 需指出的是,尽管此类工具能提高购票效率,但其使用可能涉及违反平台服务协议或相关法规的风险。各票务销售方通常对自动化抢票行为设有明确约束,因此开发与使用者均应遵守相应规定,确保技术应用的合法性。 综上所述,该基于Python的票务辅助工具是针对特定场景设计的自动化解决方案,通过技术手段改善用户购票体验,但同时也强调必须在法律与平台规则框架内合理使用此类技术。 资源来源于网络分享,仅用于学习交流使用,请勿用于商业,如有侵权请联系我删除!
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值