【ThreadLocal详解】


ThreadLocal

  ThreadLocal是一个用于实现线程数据隔离的一个类,每个线程访问时,通过Get、Set方法都会产生一个属于该线程的局部变量副本,当线程结束时,ThreadLocal及变量随着线程一起被回收。


ThreadLocal的作用

总的来说,ThreadLocal有三大用途:

  1. 保存线程上下文信息,在任何地方都可以获取(通过static关键字)
     我们可以使用static关键字,在任意地方都可以对该ThreadLocal进行获取、设置。
     例如Spring中的事务,用ThreadLocal存储了连接对象,保证一次事务的所有操作都在同一连接上。
     
  2. 线程安全,避免某些情况下需要保持同步而带来的性能损失
  
  3. 线程之间数据隔离

ThreadLocal的原理

  ThreadLocal虽然叫线程局部变量,但是它不存储任何数据,它只是一个壳子,真正的存储结构是在ThreadLocal中有一个ThreadLocalMap的一个内部类,而这个内部类却被Thread定义为了成员变量。
而ThreadLocal本身并不存储值,它只是作为key存储在Thread对象中的ThreadLocalMap中,而value则是我们存储的变量。
那么,既然真正存储数据的是Thread对象中的ThreadLocalMap,每个线程都是自己的Thread对象,那么也就达成了线程私有变量以及数据隔离。

public void set(T value) {
    // 返回当前 ThreadLocal 所在的线程
    Thread t = Thread.currentThread();
    // 返回当前线程持有的map
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        // 如果 ThreadLocalMap 不为空,则直接存储<ThreadLocal, T>键值对
        map.set(this, value);
    } else {
        // 否则,需要为当前线程初始化 ThreadLocalMap,并存储键值对 <this, firstValue>
        createMap(t, value);
    }
}

  源码解析:

  1. Set() 方法

在这里插入图片描述

主要有几个步骤:

  1. 获取当前线程
  2. 将当前线程传入getMap()方法,获取ThreadLocalMap对象
  3. 设置 key 为 [当前ThreadLocal对象],value为我们设定的值。
  4. 如果Map不存在,则创建一个Map

  这时候,我们肯定纠结,这个Map到底是什么?我们接着看:

    进入getMap() 方法:

在这里插入图片描述

    这是我们可以看到,该方法返回了当前线程的 threadLocals 属性,那我们再看看是什么:

在这里插入图片描述

    原来是在Thread对象中,看到这里是不是有种明悟,原来:

  1. set() 方法中,首先获取当前线程。
  2. 获取该线程对象中的 threadLocals 属性。
ThreadLocalMap map = getMap(t))
  1. 如果该属性存在,则往其中添加元素。
map.set(this, value)
  1. 而不存在的话,则初始化该属性 threadLocals,并将值添加进去作为初始值
createMap(t, value);

void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

    我们都知道,每个线程在Java内部都表现为一个Thread对象,而每个Thread对象都拥有 threadLocals 属性,在线程调用了 set() 方法后,会初始化该属性,并且将本线程所属的元素存入进去,这样就造成了数据隔离,每个线程有自己的数据。


    实战验证

  • 没有使用ThreadLocal的情况:

在这里插入图片描述

    我们都知道,SimpleDateFormat因为内部使用了日历对象Calendar,导致不能将其作为线程共享对象,会引发线程安全问题。
因为只有一个Calendar实例,而多线程共享该实例

在这里插入图片描述

    结果(线程1及线程2运行时设置的时间被线程3覆盖,导致出现了错误的结果):

在这里插入图片描述


  • 使用ThreadLocal:

在这里插入图片描述

    结果(因为每个线程都存着自己的那份SimpleDateFormat对象,所以不会出现并发情况):

在这里插入图片描述


    Debug

     线程1拥有的:
在这里插入图片描述

     线程2拥有的:
在这里插入图片描述

     线程3拥有的:
在这里插入图片描述

    可以看到,每个线程都拥有自己的SimpleDateFormat对象。

ThreadLocal内存泄漏

  ThreadLocal内存泄漏指的是:ThreadLocal对象被回收了,但是线程内的ThreadLocalMap成员与线程一样继续存在着,它不会回收,就出现了一个现象,就是ThreadLocalMap的key没了,Map没有了指向,但value还在,key的引用一直在就造成了内存泄漏。

  而为什么ThreadLocal对象会被回收呢,因为它是弱引用。它的生命周期更短,会在一次GC后回收掉。

在这里插入图片描述

  所以使用其最好的方式是:用完了,将其remove掉,从Thread.threadLocals(Map)中删除即可。

总结

  总的来说,ThreadLocal通过避免并发保证数据安全,将数据绑定到Thread对象上,保证了数据隔离。而在使用完毕后,必须手动将值进行remove(),否则,当线程一直运行时,容易出现内存泄漏风险。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值