ThreadLocal技术


提示:以下是本篇文章正文内容,下面案例可供参考

一、ThreadLocal的总体理解

注意:ThreadLocal虽然是本地线程,但是存储的对象依然是放在堆中的。

理解描述
创建ThreadLocal<隔离类> threadlocal = new ThreadLocal<>(隔离类);
作用ThreadLocal为每个线程都提供了局部变量,每个线程都可以通过set/get对各自局部变量进行操作,从而实现线程的数据隔离。相比较加锁保证线程安全,ThreadLocal采用了空间换时间的思想,并发性能更好。
应用场景① Spring中多线程下的singleton模式的有状态Bean的线程安全问题;

② Spring通过ThreadLocal实现了事务隔离。具体的,Spring采用ThreadLocal的方式,来保证多线程环境下,每个单线程从始至终的数据库操作使用的是一个数据库连接(key是ThreadLocal,value是connection)。同时,采用这种方式可以使Service层事务时无需感知和管理connection对象。通过Spring传播级别,巧妙地管理多个事务配置之间的切换、挂起和恢复。

③ 项目中的DateUtils工具类,主要是通过SimpleDataFormat对日期进行格式化。在多线程环境下,由于SimpleDateFormat不是线程安全的。这导致项目上线后,用户的日期可能不对。因此,可以用ThreadLocal让每个线程装载着自己的SimpleDateFormat对象,以能够线程安全的实现格式化时间的目的。

④ Session和Cookies的数据隔离也是由ThreadLocal实现的。项目中将用户的登录状态放入ThreadLocal中,管理不同用户的登录信息,从而实现了不同用户之间的用户数据线程隔离问题。
底层原理① ThreadLocal类只是一个外壳,真正存储数据的是ThreadLocal内部的ThreadLocalMap结构。ThreadLocalMap的引用是定义在Thread类中的(threadLocals)。因此,实际上ThreadLocal本身并不存储值,它只是作为key来让线程从ThreadLocalMap获取对应的Value。

② 因此,每个线程创建ThreadLocal时,实际上数据是存储在自己线程Thread类内部的threadLocals变量中的,其它线程无法拿到,从而实现了数据隔离。
数据结构③ ThreadLocalMap类虽然名字中带Map,但是它并没有实现Map接口。它底层的数据结构是一个继承了弱引用WeakReference的Entry,key是ThreadLocal,value是隔离的数据。

② Entry的底层是数组,并没有用链表。存放数据时,同样是根据key计算hashcode,然后对长度取模获得存放索引。如果存放的位置上有元素,那么就比较key值;如果key值相同,则比较key值,若key值相同则更新。若key值不相同,则循环查找下一个为空的存储位置,以此解决哈希冲突。但是在set/get并发高的时候,效率较差。
内存泄漏① 原因: 由于ThreadLocal底层存放数据的ThreadLocalMap中的Entry对象继承了弱引用。当ThreadLocal被回收&&线程被复用&&线程复用后不再调用ThreadLocal的set/get/remove方法,可能会发生内存泄露。

② 解决办法: 使用完ThreadLocal后,主动remove掉。

二、ThreadLocal的set/get底层源码

总的来说,就是获取当线程Thread类,从中获取到ThreadLocalMap。然后以当前的ThreadLocal为key,去获取对应的value。

在这里插入图片描述

public void set(T value) {
    Thread t = Thread.currentThread();// 获取当前线程
    ThreadLocalMap map = getMap(t);// 获取从当前线程t中,获取ThreadLocalMap对象
    if (map != null) // 校验对象是否为空
       //this为当前ThreadLocal
        map.set(this, value); // 不为空set
    else
        createMap(t, value); // 为空创建一个map对象
}
ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
public class Thread implements Runnable {
      ……
	//真正存储ThreadLocal值的地方
    ThreadLocal.ThreadLocalMap threadLocals = null;
    
    //多线程间共享ThreadLocal的实现方式,主线程中new一个final inheritableThreadLocals,然后多线程间共享
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null; 
}

ThreadLocal为什么不把Thread直接作为key?

  • ① 因为一个线程拥有多个变量,如果把线程做为key,那么我们还需要设置额外的标识来判断此时想取哪个value。
  • ② 如果使用ThreadLocal作为key,那么底层的ThreadLocalMap就不能扩张了,相当于一直只有一个Entey<Thread,value>;

在这里插入图片描述


三、ThreadLocalMap–底层数据结构

static class ThreadLocalMap {

        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

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

在这里插入图片描述


四、多线程中共享ThreadLocal的方式

使用InheritableThreadLocal可以实现多个线程访问ThreadLocal的值,我们在主线程中创建一个InheritableThreadLocal的实例,然后在子线程中得到这个InheritableThreadLocal实例设置的值。

public class Thread implements Runnable {
      ……
	//真正存储ThreadLocal值的地方
    ThreadLocal.ThreadLocalMap threadLocals = null;
    
    //多线程间共享ThreadLocal的实现方式,主线程中new一个final inheritableThreadLocals,然后多线程间共享
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null; 
}


五、ThreadLocal内存泄漏问题

1、内存泄漏的条件

在这里插入图片描述

2、解决方法

用完ThreadLocal就要主动remove掉;

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值