
ThreadLocal是JDK软件包提供的线程局部变量. 如果创建了ThreadLocal
ThreadLocal很容易让人看到,并假定为“本地线程”. 实际上,ThreadLocal不是线程,而是Thread的局部变量. 通过将其命名为ThreadLocalVariable也许更容易理解它.
请看官方定义: 此类提供线程局部变量. 这些变量与普通变量不同. 每个访问一个线程(通过其get或set方法)的线程都有自己的变量副本,该副本独立初始化. ThreadLocal实例通常是该类中的一个私有静态字段,并且您想将状态与线程关联(例如,用户ID或事务ID).
1 /** 2 * Sets the current thread's copy of this thread-local variable 3 * to the specified value. Most subclasses will have no need to 4 * override this method, relying solely on the {@link #initialValue} 5 * method to set the values of thread-locals. 6 * 7 * @param value the value to be stored in the current thread's copy of 8 * this thread-local. 9 */ 10 public void set(T value) { 11 Thread t = Thread.currentThread(); 12 ThreadLocalMap map = getMap(t); 13 if (map != null) 14 map.set(this, value); 15 else 16 createMap(t, value); 17 }
分析:
在调用ThreadLocal的集合(T t)时,代码将首先获取当前线程的ThreadLocalMap(ThreadLocal中的静态内部类,并且还作为Thread的成员变量存在,稍后将进一步理解)存在ThreadLocalMap,使用ThreadLocal作为地图的键,然后将要保存的值作为值放入地图中(如果地图不存在,请先创建地图,然后将其放置);
/** * Returns the value in the current thread's copy of this * thread-local variable. If the variable has no value for the * current thread, it is first initialized to the value returned * by an invocation of the {@link #initialValue} method. * * @return the current thread's value of this thread-local */ public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); //此处和set方法一致,也是通过当前线程获取对应的成员变量ThreadLocalMap,map中存放的是Entry(ThreadLocalMap的内部类(继承了弱引用))
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
分析:

我只是将对象放入地图集中,现在根据键将其取出. 值得注意的是,这里的映射不存储键值对,而是继承WeakReference
/** * Removes the current thread's value for this thread-local * variable. If this thread-local variable is subsequently * {@linkplain #get read} by the current thread, its value will be * reinitialized by invoking its {@link #initialValue} method, * unless its value is {@linkplain #set set} by the current thread * in the interim. This may result in multiple invocations of the * {@code initialValue} method in the current thread. * * @since 1.5 */ public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); }
分析:
通过getMap方法获取Thread中的成员变量ThreadLocalMap,在映射中删除相应的ThreadLocal,因为ThreadLocal(键)是一个弱引用,弱引用中的键为空,gc将恢复变量值,取看一下核心M.remove(this);方法
/** * Remove the entry for key. */ private void remove(ThreadLocal<?> key) { Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); //定义Entry在数组中的标号 for (Entry e = tab[i]; //通过循环的方式remove掉Thread中所有的Entry e != null; e = tab[i = nextIndex(i, len)]) { if (e.get() == key) { e.clear(); expungeStaleEntry(i); return; } } }


问: threadlocal用于什么场合?在什么情况下使用?

结合ThreadLocal类的正式定义,threadLocal主要满足某些变量或示例是线程隔离的,但可以在同一线程的多个类或方法中使用,并且该变量也应程结束时使用破坏. 用外行的话来说: ThreadLocal确保每个线程都有自己的数据副本,这些副本可以程结束后独立回收. 由于ThreadLocal的特性,同一线程设置在某个位置,并且可以在任何后续位置获取. 它可以用来保存线程上下文信息. 例如,常用的方法是如何将一系列后续操作与每个请求相关联,您可以使用ThreadLocal进行设置,在需要记录的任何后续方法中获取请求ID,以便对整个请求进行字符串化.
有许多使用方案,例如:
问: 如果我启动另一个线程. 子线程可以获取在主线程上设置的Threadlocal值吗?
原始ThreadLocal没有继承(或转移)特征
问: 如何解决ThreadLocal无法通过的问题?
InheritableThreadLocal,ThreadLocal的子类,InheritableThreadLocal是可传递的
/**
* 重写Threadlocal类中的getMap方法,在原Threadlocal中是返回
* t.theadLocals,而在这么却是返回了inheritableThreadLocals,因为
* Thread类中也有一个要保存父子传递的变量
*/ ThreadLocalMap getMap(Thread t) { return t.inheritableThreadLocals; }
/**
* 同理,在创建ThreadLocalMap的时候不是给t.threadlocal赋值
*而是给inheritableThreadLocals变量赋值
*
*/

void createMap(Thread t, T firstValue) { t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue); }
分析: 因为InheritableThreadLocal重写ThreadLocal中的getMap和createMap方法,所以这两个方法在Thread中维护了另一个成员变量InheritableThreadLocals. 创建线程后,将回传InheritableThreadLocals中的值;
/* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */
//Thread类中维护的成员变量,ThreadLocal会维护该变量
ThreadLocal.ThreadLocalMap threadLocals = null; /* * InheritableThreadLocal values pertaining to this thread. This map is * maintained by the InheritableThreadLocal class. */
//Thread中维护的成员变量 ,InheritableThreadLocal 中维护该变量
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
//Thread init方法中的关键代码,简单来说是将父类中inheritableThreadLocals中的值拷贝到当前线程的inheritableThreadLocals中(浅拷贝,拷贝的是value的地址引用)
if (inheritThreadLocals && parent.inheritableThreadLocals != null) this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
提示: 以上四个摘要来自其他技术博客. 我个人认为摘要是更合理的,所以我直接提取了它们
扩展:

ThreadLocal程池中易于使用: 内存泄漏,请首先查看下图

每个线程中都有一个映射. 映射的类型是ThreadLocal.ThreadLocalMap. 映射中的键是threadlocal实例. 该映射确实使用弱引用,但弱引用仅用于键. 每个键都是弱引用指向threadlocal. 当threadlocal实例设置为null时,没有对threadlocal实例的强引用,因此gc将恢复threadlocal. 但是,由于当前线程连接了一个强引用,因此无法恢复我们的值. 只有在当前线程结束之后,当前线程才会在堆栈中不存在,强引用将断开连接,并且当前线程Map值将全部由GC恢复.
因此,可以得出结论,只要gc回收了线程对象,就不会发生内存泄漏threadlocal使用实例,但是当threadLocal设置为null且线程结束时,它将不会被回收,我们认为发生内存泄漏. 实际上,这是对该概念的不一致理解,没有什么可争论的. 最可怕的是线程对象没有被回收,这导致了真正的内存泄漏. 例如,当使用线程池时,线程的末尾不会被破坏,而是会再次使用. 可能有内存泄漏.
PS.Java为了最大程度地减少内存泄漏的可能性和影响,在ThreadLocal get和set期间将清除Thread Map中的所有get null值. 因此,最令人担忧的情况是将threadLocal对象设置为null,然后开始“内存泄漏”,然后使用线程池. 该线程结束,并且线程被放回线程池而不被破坏. 不再调用get,set方法,则在此期间将发生实际内存泄漏.
JVM使用ThreadLocalMap Key作为弱引用以避免内存泄漏. JVM使用remove,get和set方法恢复弱引用. 当ThreadLocal用null键存储许多Entry时,而不是调用remove,get,set方法threadlocal使用实例,这将导致内存泄漏. 使用静态ThreadLocal时,延长ThreadLocal的生命周期,也可能导致内存泄漏. 因为静态变量是在未加载类时加载的,所以当线程结束时,静态变量可能不会被回收. 然后,与仅在使用时加载普通成员变量相比,静态生命周期将更可能导致内存泄漏危机.
参考链接:
通过以上分析,我们可以知道InheritableThreadLocal是通过Thread()的inint方法在父级和子级之间传递的,但是线程池被统一以创建线程并实现重用,从而发生以下问题: p >
ps: 何时程池中创建线程?
解决方案:
以下两个链接具有详细说明,因此我不再重复. 在后续文章中,我将优化本文并添加一些示例以帮助对其进行解释. 欢迎来到收藏集. 对这篇文章有不同的看法.
本文来自电脑杂谈,转载请注明本文网址:
http://www.pc-fly.com/a/jisuanjixue/article-219717-1.html