ThreadLocal 详解
1: ThreadLocal简介
ThreadLocal是一个将在多线程中为每个线程创建单独的变量副本的类。当使用 ThreadLocal来维护变量时,ThreadLocal 会为每个线程创建单独的变量副本,避免因多线程操作共享变量而导致的数据不一致情况。
2:ThreadLocal的简单使用
public class ThreadRunnable implements Runnable {private ThreadLocal<String> threadLocal = new ThreadLocal<>();@Overridepublic void run() {// 设置ThreadLocal的值threadLocal.set("hello world");// 获取ThreadLocal的值String value = threadLocal.get();System.out.println(value);}public static void main(String[] args) {ThreadRunnable threadRunnable = new ThreadRunnable();Thread thread = new Thread(threadRunnable);thread.start();}
}
3: ThreadLocal 源码
get() 方法
public T get() {//获取当前线程Thread t = Thread.currentThread();//从当前线程中获取threadLocals变量的信息ThreadLocalMap map = getMap(t);//如果当前线程中存在信息if (map != null) {//通过当前线程的hash值获取ThreadLocalMap.Entry的信息ThreadLocalMap.Entry e = map.getEntry(this);if (e != null) {//如果不为空,获取对应的值并返回该对象信息T result = (T)e.value;return result;}}//如果当前线程的threadLocals为null的时候,则进行初始化return setInitialValue();}
当前线程初始化方法
private T setInitialValue() {//首先初始化一个null值T value = initialValue();Thread t = Thread.currentThread();//获取当前线程threadLocals的变量值ThreadLocalMap map = getMap(t);if (map != null) {//如果不为空,则对当前线程的数据进行清空map.set(this, value);} else {//初始化ThreadLocalMap 给当前线程threadLocals变量赋值createMap(t, value);}//返回初始化的null值return value;}
set()方法
public void set(T value) {//获取当前线程Thread t = Thread.currentThread();//获取当前线程threadLocals的变量值ThreadLocalMap map = getMap(t);if (map != null)//如果已存在,则将值加入到ThreadLocalMap中map.set(this, value);else//初始化ThreadLocalMap 给当前线程threadLocals变量赋值createMap(t, value);}
remove()方法
public void remove() {//通过当前线程的hash值获取ThreadLocalMapThreadLocalMap m = getMap(Thread.currentThread());if (m != null)//ThreadLocalMap删除当前线程信息m.remove(this);}
总结: 通过源码可知,ThreadLocal类封装了线程中threadLocals变量的增删操作。其中最核心就是ThreadLocalMap结构。下面我看下ThreadLocalMap 的结构
4: ThreadLocalMap
ThreadLocalMap源码
static class ThreadLocalMap {static class Entry extends WeakReference<ThreadLocal<?>> {/** The value associated with this ThreadLocal. */Object value;Entry(ThreadLocal<?> k, Object v) {//Entry继承WeakReference,k是调用WeakReference构造方法。super(k);value = v;}}
}
方法注释上来看 :
ThreadLocalMap 是一个自定义的哈希映射,仅适用于维护线程本地值。不会在 ThreadLocal 类之外导出任何操作。为了帮助处理非常大且生存期较长的使用,哈希表条目对键使用 WeakReferences。当空间不足的时候,会删除过时信息。
总结:
从ThreadLocalMap源码和官方注释中可以知道,当空间不足的时候,WeakReferences的对象信息会被垃圾回收。这个时候value的值就无法被删除,导致内存泄露问题。由于篇幅有限,后续会继续分享内存泄露相关的问题。
5: 应用场景
ThreadLocal 通常被推荐用于以下几种情况
- 当需要在多线程的环境下保存线程安全的状态信息时候
- 当你需要在同一个线程的多个操作中传递数据,而不想通过参数的方式来传递
最为常见的场景
5.1 用户身份信息传递
public class UserContextHolder {// 使用 ThreadLocal 保存用户信息private static final ThreadLocal<User> userHolder = new ThreadLocal<>();// 将用户信息与当前线程关联public static void setUser(User user) {userHolder.set(user);}// 获取与当前线程关联的用户信息public static User getUser() {return userHolder.get();}// 移除与当前线程关联的用户信息public static void remove() {userHolder.remove();}
}
5.2 数据库连接管理
例如Mybatis中SqlSessionManager类,用于存储数据库交互信息
public class SqlSessionManager implements SqlSessionFactory, SqlSession {private final ThreadLocal<SqlSession> localSqlSession = new ThreadLocal<>();public void startManagedSession() {this.localSqlSession.set(openSession());} @Overridepublic void commit() {final SqlSession sqlSession = localSqlSession.get();if (sqlSession == null) {throw new SqlSessionException("Error: Cannot commit. No managed session is started.");}sqlSession.commit();}
}