写点什么

ThreadLocal 理解及使用

作者:Rubble
  • 2022 年 4 月 13 日
  • 本文字数:2567 字

    阅读完需:约 8 分钟

threadlocal 是一个线程内部的存储类,可以在指定线程内存储数据,数据存储以后,只有指定线程可以得到存储数据。


ThreadLocal


//set 方法public void set(T value) {      //获取当前线程      Thread t = Thread.currentThread();      //实际存储的数据结构类型      ThreadLocalMap map = getMap(t);      //如果存在map就直接set,没有则创建map并set      if (map != null)          map.set(this, value);      else          createMap(t, value);  }  //getMap方法ThreadLocalMap getMap(Thread t) {      //thred中维护了一个ThreadLocalMap      return t.threadLocals; } //createMapvoid createMap(Thread t, T firstValue) {      //实例化一个新的ThreadLocalMap,并赋值给线程的成员变量threadLocals      t.threadLocals = new ThreadLocalMap(this, firstValue);}
复制代码


通过 set 方法可以看到 当前线程保存了一个 ThreadLocalMap 类型的 threadLocals,已 threadLocal 为 key 存放了当前数据 value.


//ThreadLocal中get方法public T get() {    Thread t = Thread.currentThread();    ThreadLocalMap map = getMap(t);    if (map != null) {        ThreadLocalMap.Entry e = map.getEntry(this);        if (e != null) {            @SuppressWarnings("unchecked")            T result = (T)e.value;            return result;        }    }    return setInitialValue();}    //ThreadLocalMap中getEntry方法private Entry getEntry(ThreadLocal<?> key) {       int i = key.threadLocalHashCode & (table.length - 1);       Entry e = table[i];       if (e != null && e.get() == key)            return e;       else            return getEntryAfterMiss(key, i, e);   }
复制代码

ThreadLocalMap

set 方法
//Entry为ThreadLocalMap静态内部类,对ThreadLocal的若引用//同时让ThreadLocal和储值形成key-value的关系static class Entry extends WeakReference<ThreadLocal<?>> {    /** The value associated with this ThreadLocal. */    Object value;
Entry(ThreadLocal<?> k, Object v) { super(k); value = v; }}
//ThreadLocalMap构造方法ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) { //内部成员数组,INITIAL_CAPACITY值为16的常量 table = new Entry[INITIAL_CAPACITY]; //位运算,结果与取模相同,计算出需要存放的位置 //threadLocalHashCode比较有趣 int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); table[i] = new Entry(firstKey, firstValue); size = 1; setThreshold(INITIAL_CAPACITY);}
复制代码


每个线程 Thread 持有一个 ThreadLocalMap 类型的实例 threadLocals,结合此处的构造方法可以理解成每个线程 Thread 都持有一个 Entry 型的数组 table,而一切的读取过程都是通过操作这个数组 table 完成的。


Entry 是一个弱引用,threadLocalMap 使用 ThreadLocal 的弱引用作为 key,如果一个 ThreadLocal 不存在外部强引用时,Key(ThreadLocal)势必会被 GC 回收,这样就会导致 ThreadLocalMap 中 key 为 null, 而 value 还存在着强引用,只有 thead 线程退出以后,value 的强引用链条才会断掉。比如线程池的存在会导致内存泄露


当 ThreadLocalMap 的 key 为弱引用回收 ThreadLocal 时,由于 ThreadLocalMap 持有 ThreadLocal 的弱引用,即使没有手动删除,ThreadLocal 也会被回收。当 key 为 null,在下一次 ThreadLocalMap 调用 set(),get(),remove()方法的时候会被清除 value 值。


Remove()方法


private void remove(ThreadLocal<?> key) {    //使用hash方式,计算当前ThreadLocal变量所在table数组位置    Entry[] tab = table;    int len = tab.length;    int i = key.threadLocalHashCode & (len-1);    //再次循环判断是否在为ThreadLocal变量所在table数组位置    for (Entry e = tab[i];         e != null;         e = tab[i = nextIndex(i, len)]) {        if (e.get() == key) {            //调用WeakReference的clear方法清除对ThreadLocal的弱引用            e.clear();            //清理key为null的元素            expungeStaleEntry(i);            return;        }    }
复制代码


ThreadLocalMap 的 set 方法,当 key 为 null 时清除 value.


if (k == null) {  replaceStaleEntry(key, value, i);  return;}
复制代码


private void set(ThreadLocal<?> key, Object value) {
// We don't use a fast path as with get() because it is at // least as common to use set() to create new entries as // it is to replace existing ones, in which case, a fast // path would fail more often than not.
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)]) { ThreadLocal<?> k = e.get();
if (k == key) { e.value = value; return; }
if (k == null) { replaceStaleEntry(key, value, i); return; } }
tab[i] = new Entry(key, value); int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash();}
复制代码


避免内存泄露的方法:每次使用完 ThreadLocal 都调用它的 remove()方法清除数据。

ThreadLocal 的使用场景

最常见的场景是在拦截器 interceptor 解析用户信息,并将用户信息放入到 ThreadLocal 中。


public class UserContext {  private static ThreadLocal<User> localUser = ThreadLocal.withInitial(() -> {    return new User();  });

public static void setUser(User user) { localUser.set(user); }
public static User getUser() { return localUser.get(); }
复制代码


Spring 中的 RequestContextHolder 就是使用了 TreadLocal 存放信息。


基于 Session 的会话管理也用到了 TreadLoca,如 shiro 的 subject 内部用到了 Treadlocal。

用户头像

Rubble

关注

还未添加个人签名 2021.06.01 加入

还未添加个人简介

评论

发布
暂无评论
ThreadLocal理解及使用_4月日更_Rubble_InfoQ写作平台