1. 作用
- 主要是用作数据隔离,填充的数据只属于当前线程,变量的数据对别的线程而言是相对隔离的,在多线程环境下,可以防止自己的变量被其他线程篡改
2. 应用场景
- spring采用ThreadLocal,来保证单个线程中的数据库操作使用的是同一个数据库连接,同时,采用这种方式可以是业务层使用事务时不需要感知并管理connection对象,通过传播级别,巧妙地管理多个事务配置之间的切换,挂起和恢复。
- 在项目中一个线程经常遇到跨越若干方法调用,需要传递对象,即上下文信息,它是一种状态,经常时用户身份、任务信息等。所以可以在调用前在ThreadLocal中设置参数,其他地方调用get就可以得到上下文信息
3. 实现原理
- 每个线程对象拥有一个ThreadLocalMap类型的引用threadLocals,它是懒加载的,只有当第一次调用threadLocal实例的时候才初始化
- 这个threadLocals可以看成一个map,其中key为threadLocal实例,value为当前线程对应的存储信息。
- 调用set方法,就是以ThreadLocal对象自己作为key,资源对象作为value,放入当前线程的ThreadLocalMap集合中
- 调用get方法,就是以ThreadLocal对象自己作为key,到当前线程关联的ThreadLocalMap集合中查找对应的value
- 调用remove方法,就是以ThreadLocal对象自己作为key,到关联的ThreaLocalMap中移除对应的键值对。
1. ThreadLocal的使用
ThreadLocal<String> threadLocal= new ThreadLocal();
threadLocal.set("张三");
String name = threadLocal.get();
threadLocal.remove();
2. ThreadLocal的set方法
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对象并且进行set
}
- 首先获取当前线程实例
- 根据线程实例获取其ThreadLocalMap实例
- 如果ThreadLocalMap实例不为空,则往这个map中存入value
- 如果为空,则先创建实例,再往这个map中存入value
3. ThreadLocalMap如何解决Hash冲突
- ThreadLocalMap里面包含了一个Entry数组,当用ThreadLoaclMap存储信息的时候,首先根据ThreaLocal对象的hash值定位到Entry数组的下标
- 如果当前位置为空,则初始化一个Entry对象放在该位置上
- 如果不为空,先判断是否当前位置的key是否为ThreadLocal对象相等,
- 相等则刷新value
- 不相等则去判断下一个位置(还是先判断是否为空,再判断是否相等),直到set成功
即通过开放地址法之线性探测法来解决hash冲突。(而HashMap是通过拉链法)
4. 如何共享线程的ThreadLocal数据
可以通过在主线程创建一个InheritableThreadLocal的实例,然后子线程可以调用这个实例的值
private void test() {
final ThreadLocal threadLocal = new InheritableThreadLocal();
threadLocal.set("张三");
Thread t = new Thread() {
@Override
public void run() {
super.run();
Log.i( "name =" + threadLocal.get());
}
};
t.start();
}
原理
- Thead类中有一个inheritableThreadLocals变量
-
public class Thread implements Runnable { /* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null; /* * InheritableThreadLocal values pertaining to this thread. This map is * maintained by the InheritableThreadLocal class. */ ThreadLocal.ThreadLocalMap inheritableThreadLocals = null; }
-
它用来保存父子共享的InheritableThreadLocal实例
4. ThreadLocal的内存泄露问题
- ThreadLocal在保存的时候会把自己当作key存在ThreadLocalMap中,但是这个key被设计成了弱引用
- 这就会导致,当ThreadLocal在没有外部强引用时,发生GC时这个key就会被回收。
- 如果创建ThreadLocal的线程一直持续运行,那么这个Entry对象的value就可能一直得不到回收,会发生内存泄漏问题
- 在线程池中,一个线程处理一个任务完成后不会被销毁,而是保存起来留着以后复用,所以会导致ThreadLocalMap不会被清空,则就会发生ThreadLocal内存泄漏问题
如何解决
- 在最后一次使用ThreadLocal后调用remove方法将值给清空就可以了。
5. 补充:jdk1.8 中改进的好处
- 一般来说,一个Thread只会跟少数几个ThreadLocal关联,那么从Thread中去寻找对应的ThreadLocal的开销是比较小的
- 如果一个Thread死掉,那么它关联的threadLocals也会被自动释放掉,很大程度上避免了内存泄漏的问题。