前言
一、基础介绍
1、ThreadLocal的定义
ThreadLocal是线程Thread中属性threadLocals的管理者。
ThreadLocal叫做线程变量,意思是ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的,也就是说该变量是当前线程独有的变量。ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。
2、 使用场景
ThreadLocal 适用于每个线程需要自己独立的实例且该实例需要在多个方法中被使用,也即变量在线程间隔离而在方法或类间共享的场景。
3、ThreadLocal原理
3.1、ThreadLocal类结构及方法解析
3.2、ThreadLocal及Thread之间的关系
Thread中属性threadLocals,作为一个特殊的Map,它的key值就是我们ThreadLocal实例,而value值这是我们设置的值。
3.3 、ThreadLocal的set()方法
public void set(T value) {
//1、获取当前线程
Thread t = Thread.currentThread();
//2、获取线程中的属性 threadLocalMap ,如果threadLocalMap 不为空,
//则直接更新要保存的变量值,否则创建threadLocalMap,并赋值
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
// 初始化thradLocalMap 并赋值
createMap(t, value);
}
3.4、 ThreadLocal的get方法
public T get() {
//1、获取当前线程
Thread t = Thread.currentThread();
//2、获取当前线程的ThreadLocalMap
ThreadLocalMap map = getMap(t);
//3、如果map数据为空,
if (map != null) {
//3.1、获取threalLocalMap中存储的值
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
//如果是数据为null,则初始化,初始化的结果,TheralLocalMap中存放key值为threadLocal,值为null
return setInitialValue();
}
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
3.5 、ThreadLocal的remove方法
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
二、使用场景
1、数据库连接保证线程安全
2、存储用户Session
private static final ThreadLocal threadSession = new ThreadLocal();
public static Session getSession() throws InfrastructureException {
Session s = (Session) threadSession.get();
try {
if (s == null) {
s = getSessionFactory().openSession();
threadSession.set(s);
}
} catch (HibernateException ex) {
throw new InfrastructureException(ex);
}
return s;
}
3、数据跨层传递(controller,service, dao)
public class ActionDataManager {
/*请求头参数*/
ThreadLocal<String> headThreadLocal = new ThreadLocal<String>();
/*请求体参数*/
ThreadLocal<String> bodyThreadLocal = new ThreadLocal<String>();
}
//统一HTTP请求接口处理
class HttpController{
public void process(...){
解析参数...
//给ThreadLocal赋值,后续的服务直接通过ThreadLocal获取就行了。
ActionDataManager .headThreadLocal .set(head);
new Service().service();
}
}
class Service{
public void service(){
String head = ActionDataManager .headThreadLocal .get();
...
}
}
三、使用注意
1、 各种引用与GC的关系
类型 | 回收时间 | 应用场景 |
---|---|---|
强引用 | 一直存活,除非GC Roots不可达 | 所有程序的场景,基本对象,自定义对象等 |
软引用 | 内存不足时会被回收 | 一般用在对内存非常敏感的资源上,用作缓存的场景比较多,例如:网页缓存、图片缓存 |
弱引用 | 只能存活到下一次GC前 | 生命周期很短的对象,例如ThreadLocal中的Key。 |
虚引用 | 随时会被回收, 创建了可能很快就会被回收 | 可能被JVM团队内部用来跟踪JVM的垃圾回收活动 |
2、使用ThreadLocal可能导致的内存泄漏
因为ThreadLocal在ThreadLocalMap中作为key时是弱应用WeakReference,所以可能导致key被回收,而值无法再使用导致内存泄漏。
- ThreadLocal引用被设置为null,且后面没有set,get,remove操作。
- 线程一直运行,不停止。(线程池)
- 触发了垃圾回收。(Minor GC或Full GC)
避免ThreadLocal导致内存泄漏的方法:
-
ThreadLocal申明为private static final。
Private与final 尽可能不让他人修改变更引用,
Static 表示为类属性,只有在程序结束才会被回收。 -
ThreadLocal使用后务必调用remove方法。
最简单有效的方法是使用后将其移除。
3、ThreadLocal与Synchronized的区别
ThreadLocal其实是与线程绑定的一个变量。ThreadLocal和Synchonized都用于解决多线程并发访问。
但是ThreadLocal与synchronized有本质的区别:
- Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离。
- Synchronized是利用锁的机制,使变量或代码块在某一时该只能被一个线程访问。而ThreadLocal为每一个线程都提供了变量的副本,使得每个线程在某一时间访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享。而Synchronized却正好相反,它用于在多个线程间通信时能够获得数据共享。