重点
TheadLocal:用于线程间变量隔离,仅能在当前线程内访问,无法使用ThreadLocal进行父子线程变量传递。
ThreadLocalMap定义在ThreadLocal中的内部类,Thread中存在一个ThreadLocalMap属性
ThreadLocal内部维护的ThreadLocalMap是与当前线程绑定(ThreadLocalMap是当前线程的一个属性)
添加数据时,map的key为当前ThreadLocal实例;一个线程可中可进行多个ThreadLocal创建
当元素冲突时,采用开放寻址方式,而非链表方式。
一、应用场景
在使用多线程情况下,程序在处理用户请求的时候,通常后端服务器是有一个线程池,对每一个请求就分配一个线程来处理,那为了防止多线程并发处理请求的时,发生线程数据安全问题(串数据),利用ThreadLocal实现线程间变量隔离。
使用案例
//存放用户信息的ThreadLocal,一般使用Util类进行封装private static final ThreadLocal<UserInfo> userInfoThreadLocal = new ThreadLocal<>();public Response handleRequest(UserInfo userInfo) {Response response = new Response();try {// 1.用户信息set到线程局部变量中userInfoThreadLocal.set(userInfo);doHandle();} finally {// 3.使用完移除掉userInfoThreadLocal.remove();}return response;}//业务逻辑处理private void doHandle () {// 2.实际用的时候取出来UserInfo userInfo = userInfoThreadLocal.get();//处理业务操作....}
二、实现细节
实现总结
ThreadLocal
单个线程可以进行多次执行new ThreadLocal()操作,每次调用ThreadLocal的set()方法或者setInitialValue()方法,都会在内部的ThreadLocalMap中添加一条数据(类似于执行了一次put操作)
key:当前ThreadLocal对象
value:需要存储的线程局部变量
ThreadLocalMap【Thread的一个成员变量】

当发生hash冲突时,采用开放地址法进行解决冲突
ThreadLocalMap底层使用Entry数组,初始大小16。其中Entry继承WeakReference,仅将Entry的key设置为弱类型**。
static class Entry extends WeakReference<ThreadLocal<?>> {/** The value associated with this ThreadLocal. */Object value;Entry(ThreadLocal<?> k, Object v) {//调用父类构造方法,将key设置为弱引用类型super(k);value = v;}}public WeakReference(T referent) {super(referent);}
key设置为弱引用原因
为了尽最避免内存泄漏,但是如果仅仅这样,还是无法避免内存泄漏。ThreadLocalMap是根据key是否为null来判断是否清理Entry???(没找到依据)。
系统一般使用线程池将线程进行复用,线程生命周期很长。根据GC root搜索方式,进行垃圾回收,Thread—>ThreadLocalMap-Entry会一直存在。
**
ThreadLocal的设计者认为只要ThreadLocal 所在的作用域结束了工作被清理了(但有时ThreadLocal并不会顺利清理),GC回收的时候就会把key引用对象回收,key置为null,ThreadLocal会尽力保证Entry清理掉来最大可能避免内存泄漏。
ThreadLocal 所在的作用域结束,并没有清理时,TheadLocal会一直被强引用关联
**
正常初始化时引用结构
弱引用
只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描内存区域时,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。
具体分析
实例化方式1/*** Creates a thread local variable.* @see #withInitial(java.util.function.Supplier)*/public ThreadLocal() {}实例化方式2public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {return new SuppliedThreadLocal<>(supplier);}//在ThreadLocal中设置变量private T setInitialValue() {T value = initialValue();Thread t = Thread.currentThread();//获取当前线程关联的ThreadLocalMap实例ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);else//初始化ThreadLocalMapcreateMap(t, value);return value;}ThreadLocalMap getMap(Thread t) {return t.threadLocals;}void createMap(Thread t, T firstValue) {t.threadLocals = new ThreadLocalMap(this, firstValue);}
三、存在问题与避免
内存泄漏
Entry 继承了WeakReference类,Entry 中的 key 是WeakReference类型的(Value为强引用),在Java 中当对象只被 WeakReference 引用,没有其他对象引用时,被WeakReference 引用的对象发生GC 时会直接被回收掉。
解决方案
手动调用remove函数
class Threadlocal {public void remove() {//获取当前线程的ThreadLocalMapThreadLocalMap m = getMap(Thread.currentThread());if (m != null)m.remove(this); //this就是ThreadLocal对象,移除,方法在下面}}class ThreadlocalMap {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;}}}}//清理数组元素private int expungeStaleEntry(int staleSlot) {Entry[] tab = table;int len = tab.length;//把staleSlot的value置为空,然后数组元素置为空tab[staleSlot].value = null;tab[staleSlot] = null;size--; //元素个数-1// Rehash until we encounter nullEntry e;int i;for (i = nextIndex(staleSlot, len);(e = tab[i]) != null;i = nextIndex(i, len)) {ThreadLocal<?> k = e.get();//k 为null代表引用对象被GC回收掉了if (k == null) {e.value = null;tab[i] = null;size--;} else {//因为元素个数减少了,就把后面的元素重新hashint h = k.threadLocalHashCode & (len - 1);//hash地址不相等,就代表这个元素之前发生过hash冲突(本来应该放在这没放在这),//现在因为有元素被移除了,很有可能原来冲突的位置空出来了,重试一次if (h != i) {tab[i] = null;//继续采用链地址法存放元素while (tab[h] != null)h = nextIndex(h, len);tab[h] = e;}}}return i;}
