ThreadLocal笔记

Dcr 1年前 ⋅ 848 阅读

 

ThreadLocal 的作用主要是做数据隔离,填充数据只属于当前线程,变量的数据对别的线程而言是相对隔离的.
案例:
Spring实现事务隔离级别的源码-->Spring采用Threadlocal的方式,来保证单个线程中的数据库操作使用的是同一个数据库连接,同时,采用这种方式可以使业务层使用事务时不需要感知并管理connection对象,通过传播级别,巧妙地管理多个事务配置之间的切换,挂起和恢复。
Spring使用ThreadLocal实现隔离主要是在TransactionSynchronizationManager
Spring的事务主要通过ThreadLocal和AOP实现. --> 每个线程自己的链接通过TreadLocal保存

其它用途传递参数(类似上下文传递参数),很多场景的cookie,session等数据隔离都是通过ThreadLocal实现.

用法:

public void set(T value) {    
		Thread t = Thread.currentThread();// 获取当前线程    
		ThreadLocalMap map = getMap(t);// 获取ThreadLocalMap对象    
		if (map != null) // 校验对象是否为空        
			map.set(this, value); // 不为空set    
		else        
			createMap(t, value); // 为空创建一个map对象
	}


ThreadLocalMap是当前线程Thread的threadLocals的变量中取得
每个线程THread都维护了自己的threadLocals变量,所以每个线程创建ThreadLocal的时候,实际上数据是存在自己线程Thread的threadLocals变量里面,从而实现隔离

ThreadLocalMap 底层结构:
数据结构类似HashMap的数组,但并未实现Map接口,而他的Entry继承了WeakReference(弱引用)
问题:
A.为什么使用数组,没有链表怎么解决Hash冲突?
1.一个线程可以有多个ThreadLocal来存放不同类型的对象,他们最后都将放到ThreadLocalMap里,所以需要用数组存储.
2.Hash冲突:
ThreadLocalMap在存储的时候会给每一个ThreadLocal对象一个threadLocalHashCode,在插入过程中,根据ThreadLocal对象的hash值,定位到table中的位置i,int i = key.threadLocalHashCode & (len - 1)
a.如果当前位置为空则初始化一个Entry对象防止i位置上;
b.若该位置不为空而Entry对象的key等于即将设置的key则覆盖Entry中的value;
c.如果位置i不为空,且key不等,就找下一个空位置,直到为空为止
这里需要注意如果冲突严重会导致get和set效率很低.
B.对象存放位置
栈内存归属于单个线程,线程与栈内存为一对一关系,其存储变量只在所属线程内可见
C.ThreadLocal的实例以及其值是否存放在栈上?
不是,因为ThreadLocal实例实际上也是被其创建的类持有(更顶端应该是被线程持有),而ThreadLocal的值其实也是被线程实例持有,它们都位于堆上,只是将可见性修改为线程可见.
D.如何共享线程的ThreadLocal数据,以及如何传递的.
1.通过InheritableThreadLocal可以实现多个线程访问ThreadLocal的值,在主线程中创建一个InheritableThreadLocal的实例,然后在子线程中得到这个InheritableThreadLocal实例设置的值
2.通过Thread内部声明的inheritableThreadLocals变量传递,如果父线程和子线程的inheritThreadLocals都存在,则把父线程的inheritThreadLocals给当前线程的inheritThreadLocals.

ThreadLocal存在的问题:
内存泄漏
ThreadLocal在保存的时候会把自己当做Key存在ThreadLocalMap中,正常情况应该是key和value都应该被外界强引用才对,但是现在key被设计成WeakReference弱引用了。(key是弱引用被GC了,但是value还在)
这就导致了一个问题,ThreadLocal在没有外部强引用时,发生GC时会被回收,如果创建ThreadLocal的线程一直持续运行,那么这个Entry对象中的value就有可能一直得不到回收,发生内存泄漏.
就比如线程池里面的线程,线程都是复用的,那么之前的线程实例处理完之后,出于复用的目的线程依然存活,所以,ThreadLocal设定的value值被持有,导致内存泄露。
解决
使用完后remove把值清空.

资料参考:

https://juejin.im/post/6854573219916021767

全部评论: 0

    我有话说: