什么是ThreadLocal?
ThreadLocal类顾名思义可以理解为线程本地变量,也就是说,如果定义了一个ThreadLocal,每个线程往这个ThreadLocal中读写数据是线程隔离的,互相之间不会影响。它提供了一种将可变数据通过每个线程有自己的独立副本从而实现线程封闭的机制。
它大致的实现思路是怎样的?
Thread类有一个类型为ThreadLocal.ThreadLocalMap的实例变量threadLocals,也就是说每个线程有一个自己的ThreadLocalMap。ThreadLocalMap有自己的独立实现,可以简单地将它的key视作ThreadLocal,value为代码中放入的值(实际上key并不是ThreadLocal本身,而是它的一个弱引用)。每个线程在往某个ThreadLocal里存值的时候,都会往自己的ThreadLocalMap里存,读也是以某个ThreadLocal作为引用,在自己的map里找对应的key,从而实现了线程隔离。
ThreadLocalMap的key保存的是ThreadLocal对象在内存中的地址值,key指向ThreadLocal对象,即ThreadLocalMap的key与ThreadLocal对象之间存在一种引用关系,这种引用关系底层是通过WeakReference实现的,是一种弱引用。
ThreadLocal 提供了线程本地的实例,ThreadLocal变量与普通变量的区别在于,每个使用该变量的线程都会初始化一个完全独立的实例副本。ThreadLocal 变量通常被private static修饰。当一个线程结束时,它所使用的所有 ThreadLocal 相对的实例副本都可被回收。
在多线程高并发的情况下,如果想让一个变量不被多个线程共享,那就将这个变量定义成局部变量放在方法中,因为局部变量是存储在线程的虚拟机栈中的,只有所属的线程可以操作。如果想在多个方法或类之间都可以操作这个变量,可以将该变量作为方法的参数进行传递,这样多个线程在调用这些方法时,都只是操作自己虚拟机栈中的变量。但是,这样在每个需要使用该变量的方法上都加上这个变量作为参数,显得不够优雅和美观。此时就可以使用ThreadLocal来保存该变量。
ThreadLocal 适用于每个线程需要自己独立的实例且该实例需要在多个方法中被使用,也即变量在线程间隔离而在方法或类间共享的场景。
ThreadLocal 变量的基本原理是,同一个 ThreadLocal 所包含的对象(对ThreadLocal< String >而言即为String类型变量),在不同的Thread中有不同的副本。这里需要注意:每个Thread内有自己的实例副本,且该副本只能由当前Thread使用。这也是 ThreadLocal命名的由来。
既然每个Thread有自己的实例副本,且其它Thread不可访问,那就不存在多线程间共享的问题。既无共享,何来同步问题,又何来解决同步问题一说?
所以,ThreadLocal 并不解决多线程共享变量的问题,也不解决同步的问题。
Thread维护ThreadLocal与实例的映射关系
在Thread中有一个ThreadLocalMap属性,通过ThreadLocalMap来维护ThreadLocal与要保存的实例的映射关系,即以ThreadLocal对象为键(实际上是以ThreadLocal的弱引用作为键),以实例对象为值保存到ThreadLocalMap中。每个线程都有一个ThreadLocalMap,线程只能访问自己的ThreadLocalMap对象,该对象不会在多个线程中共享,也就不存在线程安全的问题。ThreadLocalMap中通过Entry数组保存线程中多个ThreadLocal对象。ThreadLocalMap底层数据结构是数组,而HashMap底层数据结构是数组+链表+红黑树。下图中的红色虚线表示弱引用。
1、源码分析
ThreadLocalMap类的定义是在ThreadLocal类中,ThreadLocalMap是ThreadLocal的静态内部类,对ThreadLocalMap真正的引用却是在Thread类中。
public final int getAndAdd(int var1) {
return unsafe.getAndAddInt(this, valueOffset, var1);
}
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2); //获取当前threadLocalHashCode的值
//让当前的值var5加上var4之后,替换成最新的值。如果失败,则反复尝试
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5; //返回当前新增ThreadLocal的threadLocalHashCode的值
}
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
由上面的源码可知,ThreadLocal类中有一个int类型的threadLocalHashCode变量(可以简单理解为ThreadLocal的哈希值),该变量的值是通过AtomicInteger类中的getAndAdd方法得到的。另外还有一个int类型的HASH_INCREMENT常量。每个ThreadLocal的threadLocalHashCode变量的值都不一样。当创建一个新的ThreadLocal时,新ThreadLocal的threadLocalHashCode的值是通过getAndAddInt方法中的getIntVolatile方法得到的。同时将新ThreadLocal的threadLocalHashCode的值加上HASH_INCREMENT常量后替换掉新ThreadLocal的threadLocalHashCode的值,用于作为下一个新增ThreadLocal的threadLocalHashCode的值。
1.1、ThreadLocalMap
ThreadLocalMap是ThreadLocal的静态内部类,Entry是ThreadLocalMap的静态内部类,继承了WeakReference。ThreadLocalMap中有一个ThreadLocal.ThreadLocalMap.Entry[] table属性,该属性是一个Entry数组,初始容量默认是16。源码中使用ThreadLocalMap构造函数创建Entry[16]数组。
Entry继承WeakReference,包括两个元素,一个ThreadLocal<?>类型的成员referent和一个Object类型的成员value(需要保存的数据)。其中成员变量referent是一个指向ThreadLocal对象的弱引用,其特点是,当一个对象只有弱引用而无强引用指向它时,JVM GC时会立即回收该对象。
static class ThreadLocalMap {
/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
/**
* The initial capacity -- MUST be a power of two.
*/
private static final int INITIAL_CAPACITY = 16;
/**
* The table, resized as necessary.
* table.length MUST always be a power of two.
*/
private Entry[] table;
/**
* The number of entries in the table.
*/
private int size = 0;
/**
* The next size value at which to resize.
*/
private int threshold; // Default to 0
/**
* Set the resize threshold to maintain at worst a 2/3 load factor.
*/
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
/**
* Increment i modulo len.
*/
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
/**
* Decrement i modulo len.
*/
private static int prevIndex(int i, int len) {
return ((i - 1 >= 0) ? i - 1 : len - 1);
}
/**
* Construct a new map initially containing (firstKey, firstValue).
* ThreadLocalMaps are constructed lazily, so we only create
* one when we have at least one entry to put in it.
*/
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
}
这里贴一下WeakReference的源码(T referent是抽象父类Reference<T>中的属性):
public class WeakReference<T> extends Reference<T> {
/**
* Creates a new weak reference that refers to the given object. The new
* reference is not registered with any queue.
*
* @param referent object the new weak reference will refer to
*/
public WeakReference(T referent) {
super(referent);
}
/**
* Creates a new weak reference that refers to the given object and is
* registered with the given queue.
*
* @param referent object the new weak reference will refer to
* @param q the queue with which the reference is to be registered,
* or <tt>null</tt> if registration is not required
*/
public WeakReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}
}
ThreadLocalMap底层结构:猜想他的数据结构像HashMap一样使用数组+链表,但是看源码可以发现,它并未实现Map接口,而且他的Entry是继承WeakReference(弱引用)的,也没有看到HashMap中的next,所以不存在链表。用数组是因为开发过程中一个线程可以有多个TreadLocal来存放不同类型的对象,但是他们都将放到当前线程的ThreadLocalMap里,所以肯定要用数组来存。
1.2、set方法
ThreadLocal对象的set方法的第一条语句是获取当前线程对象,第二条语句是获取当前线程的ThreadLocalMap对象,因此,任何线程调用ThreadLocal对象的set方法保存对象时,都是保存到自身线程的ThreadLocalMap对象中。另外,ThreadLocal对象的get方法的前两条语句也是这两条,因此,任何线程调用ThreadLocal对象的get方法获取对象时,都是从自身线程的ThreadLocalMap对象中获取。remove方法首先也是获取当前线程的ThreadLocalMap对象。
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
1、set方法中的第一条语句是获取当前线程,如果当前线程是主线程,则Thread.currentThread()的值就是main线程对象(Thread.currentThread().getName()方法得到是线程的名称,如main),如果是自定义的线程,则Thread.currentThread()的值就是自定义线程对象。
2、然后通过getMap方法获取当前线程对象中的ThreadLocalMap对象,即ThreadLocalMap threadLocals = null;第一次调用ThreadLocal的set方法时,ThreadLocalMap对象就是null,所以调用createMap方法创建该对象。
3、如果ThreadLocalMap对象为null,则调用createMap方法,以ThreadLocal对象为键,以value对象为值,创建ThreadLocalMap对象。在createMap方法中调用了ThreadLocalMap的构造器来创建ThreadLocalMap对象,如下所示。
由上面分析可知,每个ThreadLocal的threadLocalHashCode变量的值都不一样。新增ThreadLocal的threadLocalHashCode变量的值是由上一个ThreadLocal的threadLocalHashCode变量的值得到的,即旧ThreadLocal的threadLocalHashCode变量的值加上HASH_INCREMENT常量就是新ThreadLocal的threadLocalHashCode的值。threadLocalHashCode可以简单理解为ThreadLocal的哈希值。
/**
* Construct a new map initially containing (firstKey, firstValue).
* ThreadLocalMaps are constructed lazily, so we only create
* one when we have at least one entry to put in it.
*/
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
//创建一个初始容量为16的ThreadLocal.ThreadLocalMap.Entry[]数组
table = new ThreadLocal.ThreadLocalMap.Entry[INITIAL_CAPACITY];
//ThreadLocal的哈希值与数组的长度取模,获取Entry(firstKey, firstValue)在数组中的索引
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
//创建一个新的Entry对象(以firstKey对象为键,以firstValue对象为值)放到Entry[]数组中
table[i] = new ThreadLocal.ThreadLocalMap.Entry(firstKey, firstValue);
size = 1;
//设置临界值
setThreshold(INITIAL_CAPACITY);
}
//设置临界值,当Entry[]数组中元素个数超过该值时,将对数组进行扩容
private void setThreshold(int var1) {
this.threshold = var1 * 2 / 3;
}
调用ThreadLocalMap的构造函数创建ThreadLocalMap对象的过程如下图所示:
由上面分析可知,Entry是ThreadLocalMap类中的静态内部类,如下所示。此处调用Entry的构造方法创建新的Entry对象,两个参数分别是ThreadLocal对象(实际是ThreadLocal对象的弱引用)和要存放的数据对象。
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
Entry将ThreadLocal对象作为参数继续调用父类WeakReference<T>的构造函数:
public class WeakReference<T> extends Reference<T> {
/**
* Creates a new weak reference that refers to the given object. The new
* reference is not registered with any queue.
*
* @param referent object the new weak reference will refer to
*/
public WeakReference(T referent) {
super(referent);
}
/**
* Creates a new weak reference that refers to the given object and is
* registered with the given queue.
*
* @param referent object the new weak reference will refer to
* @param q the queue with which the reference is to be registered,
* or <tt>null</tt> if registration is not required
*/
public WeakReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}
}
WeakReference<T>继续调用抽象父类Reference<T>的构造方法:
private T referent;
Reference(T referent) {
this(referent, null);
}
Reference(T referent, ReferenceQueue<? super T> queue) {
this.referent = referent;
this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
}
所以在调用createMap方法时,最终会将ThreadLocal对象的引用传递给Reference<T>类的referent实例变量,即referent指向了ThreadLocal对象,这是一个弱引用。在下面的remove方法中,就是将referent指向null,主动断开referent对ThreadLocal对象的引用,同时将Entry的value属性赋值为null,断开value与保存对象之间的强引用。并将entry赋值为null。如果不主动断开,当不存在对ThreadLocal对象的强引用时,在垃圾回收器回收对象时会回收只被弱引用指向的对象,此时referent会指向null,就无法根据key(即ThreadLocal对象的引用)从Entry中找到对应的value,这时value引用的对象就再也无法获取到,但是value对该对象是强引用,GC无法回收该对象,就会导致内存泄漏。
4、如果ThreadLocalMap对象不为null,则调用ThreadLocalMap对象的set方法,ThreadLocalMap的set方法如下所示。
/**
* Set the value associated with key.
*
* @param key the thread local object
* @param value the value to be set
*/
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;
//ThreadLocal的哈希值与数组的长度取模,获取Entry(key,value)对象在数组中的索引
int i = key.threadLocalHashCode & (len-1);
//如果位置i的Entry对象是null,则不进入for循环
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
//获取索引i位置的Entry中的ThreadLocal对象
ThreadLocal<?> k = e.get();
//如果待保存数据的ThreadLocal对象与索引i位置已存在的ThreadLocal对象相等,则替换成新值
if (k == key) {
e.value = value;
return;
}
//向后寻找ThreadLocal对象为null的索引位置来存储(key,value)
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
//如果位置i是空的,没有Entry对象,就初始化一个Entry对象放在位置i上
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
(1)如果位置i是空的,没有Entry对象,就初始化一个Entry对象放在位置i上;
(2)如果位置i已经存在Entry对象,且该对象的key正好是即将设置的key,那么就刷新Entry中的value;
(3)如果位置i已经存在Entry对象,且该对象的key不等于即将设置的key,那就从位置i向后找一个空位置,初始化一个Entry对象进行存储。
ThreadLocalMap对象的set方法的作用就是将以ThreadLocal对象为键,以value对象为值保存到ThreadLocalMap对象的Entry数组中。例如下面语句代码,
ThreadLocal<String> stringLocal = new ThreadLocal<String>();
stringLocal.set("aaa");
这两行代码表示以stringLocal为键,以aaa为值,存放到ThreadLocalMap对象中。
1.3、get方法
/**
* 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);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
/**
* Get the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @return the map
*/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
/**
* Get the entry associated with key. This method
* itself handles only the fast path: a direct hit of existing
* key. It otherwise relays to getEntryAfterMiss. This is
* designed to maximize performance for direct hits, in part
* by making this method readily inlinable.
*
* @param key the thread local object
* @return the entry associated with key, or null if no such
*/
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);
}
/**
* Version of getEntry method for use when key is not found in
* its direct hash slot.
*
* @param key the thread local object
* @param i the table index for key's hash code
* @param e the entry at table[i]
* @return the entry associated with key, or null if no such
*/
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
while (e != null) {
ThreadLocal<?> k = e.get();
if (k == key)
return e;
if (k == null)
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
/**
* Variant of set() to establish initialValue. Used instead
* of set() in case user has overridden the set() method.
*
* @return the initial value
*/
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;
}
protected T initialValue() {
return null;
}
1、get方法的第一条语句是获取当前线程对象,第二条语句是获取当前线程对象的ThreadLocalMap对象。
2、如果ThreadLocalMap对象不为null,就调用该对象的getEntry方法,并将this作为参数传递(注意:此处this表示调用get方法的对象,由于get方法在ThreadLocal类中,所以只能是ThreadLocal对象可以调用get方法,所以this表示ThreadLocal对象)。在getEntry方法中,会获取到ThreadLocal.ThreadLocalMap.Entry对象,通过Entry对象的get()方法获取ThreadLocal对象,判断这个ThreadLocal对象与方法参数中的ThreadLocal对象是否相等,如果相等,就返回Entry对象,否则就调用getEntryAfterMiss方法。其实在getEntryAfterMiss方法中仍是根据上述逻辑判断下一个不为空的Entry对象,最终得到的就是以this为键的Entry对象。如果Entry对象不为空,则获取Entry对象的value属性值,并返回。
3、如果ThreadLocalMap对象为null,就调用setInitialValue方法进行初始化(与set方法类似),初始化完成之后返回一个null值。
所以get方法的作用是:根据ThreadLocal对象,从ThreadLocalMap中获取以ThreadLocal对象为键对应的值,如果ThreadLocalMap为空,则返回null。
例如以下语句:
ThreadLocal<String> stringLocal = new ThreadLocal<String>();
stringLocal.set("aaa");
stringLocal.get();
前两行代码表示以stringLocal为键,以aaa为值,存放到ThreadLocalMap对象中。第三行代码表示根据stringLocal从ThreadLocalMap中获取对应的值,即此处的aaa。
下面简单看一下Entry.get()方法得到ThreadLocal对象的源码。
ThreadLocalMap.Entry类继承了WeakReference<T>类,WeakReference类继承了Reference类,Reference类中有一个泛型类型T的变量referent,以及得到该变量的get方法,所以Entry对象调用get方法时得到的就是T类型的变量。由于static class Entry extends WeakReference<ThreadLocal<?>>,此时WeakReference<T>类中的T就是此处的ThreadLocal,所以entry.get方法得到的是ThreadLocal类型的变量。
下面对get方法进行简单梳理:
线程首先通过getMap方法获取自身的ThreadLocalMap。从getMap方法的定义可见,ThreadLocalMap的实例是Thread类的一个属性,即由Thread维护ThreadLocal对象与保存的数据对象的映射关系ThreadLocalMap(可简单认为是一个Map)。
获取到ThreadLocalMap后,通过map.getEntry(this)方法获取该ThreadLocal在当前线程的 ThreadLocalMap中对应的Entry。该方法中的this即当前访问的 ThreadLocal 对象。
如果获取到的Entry不为null,则从Entry中取出值返回。如果获取到的Entry为null,则通过setInitialValue()方法设置该ThreadLocal 对象对应的初始值null,并返回null。
简单看一个多线程访问ThreadLocal变量的例子。
假设定义一个ThreadLocal变量,如ThreadLocal<String> stringLocal = new ThreadLocal<> ();可能有多个线程对stringLocal变量进行访问,假设线程t1和t2都需要使用stringLocal,在t1中的ThreadLocalMap存放的值是{stringLocal=”aaa”},在t2中的ThreadLocalMap存放的值是{stringLocal=”bbb”},在t1线程中ThreadLocalMap(每个线程都有自己的ThreadLocalMap)的键是stringLocal,在t2线程中ThreadLocalMap的键也是stringLocal,虽然在每个线程中ThreadLocalMap的键都是stringLocal对象,但是每个线程都是操作自身ThreadLocalMap的值,彼此互不影响。
1.4、remove方法
public void remove() {
ThreadLocal.ThreadLocalMap var1 = this.getMap(Thread.currentThread());
if (var1 != null) {
var1.remove(this); //调用ThreadLocalMap的remove方法
}
}
ThreadLocal.ThreadLocalMap getMap(Thread var1) {
return var1.threadLocals;
}
//调用ThreadLocalMap的remove方法
private void remove(ThreadLocal<?> var1) {
ThreadLocal.ThreadLocalMap.Entry[] var2 = this.table;
int var3 = var2.length;
int var4 = var1.threadLocalHashCode & var3 - 1;
for(ThreadLocal.ThreadLocalMap.Entry var5 = var2[var4]; var5 != null; var5 = var2[var4 = nextIndex(var4, var3)]) {
if (var5.get() == var1) { //找到key为var1的Entry,然后进行清除
var5.clear(); //调用Entry的clear方法,即Reference的clear方法
this.expungeStaleEntry(var4); //调用ThreadLocalMap的expungeStaleEntry方法
return;
}
}
}
//调用Entry的clear方法,即Reference的clear方法
public void clear() {
this.referent = null;
}
Reference的clear方法的作用是将Entry的键指向null,为了在expungeStaleEntry方法中将key为null的Entry进行清除。主要的清除工作在expungeStaleEntry方法中进行,其实在上面的set和get方法中都调用了expungeStaleEntry方法。
//调用ThreadLocalMap的expungeStaleEntry方法
private int expungeStaleEntry(int var1) {
ThreadLocal.ThreadLocalMap.Entry[] var2 = this.table;
int var3 = var2.length;
var2[var1].value = null; //在clear方法中将key指向了null,此处将value赋值为null
var2[var1] = null; //将entry指向null(至此已将ThreadLocal保存的对象删除了)
--this.size;
//下面把索引var1之后的key为null的entry都清理了,并且顺带将一些有哈希冲突的entry给填充回可用的索引中
ThreadLocal.ThreadLocalMap.Entry var4;
int var5;
for(var5 = nextIndex(var1, var3); (var4 = var2[var5]) != null; var5 = nextIndex(var5, var3)) {
ThreadLocal var6 = (ThreadLocal)var4.get();
if (var6 == null) {
var4.value = null;
var2[var5] = null;
--this.size;
} else {
int var7 = var6.threadLocalHashCode & var3 - 1;
if (var7 != var5) {
for(var2[var5] = null; var2[var7] != null; var7 = nextIndex(var7, var3)) {
;
}
var2[var7] = var4;
}
}
}
return var5;
}
remove方法的关键就在于在clear方法中主动断开entry的key的引用连接,这样在后续的expungeStaleEntry方法中,就会将这种key为null的entry给设置为null,方便GC对内存进行回收。
remove方法的使用:
ThreadLocal<String> stringLocal = new ThreadLocal<String>();
stringLocal.set("aaa");
stringLocal.remove();
第一行代码在堆中创建了一个ThreadLocal对象,在虚拟机栈中创建了一个ThreadLocal类型的引用stringLocal,并将stringLocal引用指向了堆中的ThreadLocal对象。第二行代码表示以stringLocal为键,以aaa为值,存放到ThreadLocalMap对象中(底层是将stringLocal的引用赋值给Entry对象的referent属性,将"aaa"对象的引用赋值给Entry的value属性)。第三行代码表示根据stringLocal从ThreadLocalMap中先将Entry的键referent指向null(referent是Reference类中的实例变量,Entry间接继承了Reference,故Entry就有了referent属性。但是stringLocal并未指向null,stringLocal一直是指向new ThreadLocal对象的),然后将值value设置为null(本来值是指向"aaa"字符串对象的,现在"aaa"没有被任何引用变量使用,GC可以对其进行回收了),接着将键为stringLocal,值为"aaa"的Entry对象设置为null。至此,对"aaa"的清除工作已经完成,等待着GC对其回收。另外,在stringLocal键对应的索引处的Entry对象被清除之后,继续向下一个索引位置查询,在这个过程中遇到的key为null的Entry都会被擦除。
2、内存泄露
通常使用ThreadLocal都是作为一个类的实例变量来用的,假如在A类中有如下定义:
private ThreadLocal<String> local = new ThreadLocal<>();
由于ThreadLocal类型的引用变量local是A类对象的实例变量,在堆内存中为A类对象开辟一块内存空间,保存A对象的信息,其中包括local变量。new ThreadLocal对象被保存在堆内存中另一片内存空间,引用变量local对该ThreadLocal对象是强引用。如果A类对象不再被其他对象引用,根据可达性分析算法可知,A类对象不可达,引用的ThreadLocal对象也不可达,则GC会对A类对象和ThreadLocal对象占用的内存空间进行回收。
再来看一下ThreadLocal.ThreadLocalMap.Entry的源码:
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> var1, Object var2) {
super(var1);
this.value = var2;
}
}
由ThreadLocal.ThreadLocalMap.Entry的源码可知,Entry对键是弱引用,对值是强引用。使用弱引用的原因在于,当没有强引用指向 ThreadLocal 对象时,那么系统在进行垃圾回收的时候,这个ThreadLocal势必会被回收。此时,Entry的key指向null,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链:
Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value
永远无法回收,造成内存泄露,直至Thread被销毁后,才会被回收。下图虚线表示弱引用,实线表示强引用。
可以从下面两个方面解决内存泄漏:
1、需要使用者手动调用ThreadLocal的remove函数,手动删除不再需要的ThreadLocal,防止内存泄露。
2、JDK建议将ThreadLocal变量定义成private static的,这样ThreadLocal变量的生命周期就更长,与所在类的生命周期相同,由于一直存在对ThreadLocal对象的强引用,所以ThreadLocal对象就不会被回收,也就能保证任何时候都能根据ThreadLocal的弱引用访问到Entry的value值,然后remove它,防止内存泄露。
静态变量随着类的加载被加载,存放于方法区中(从jdk1.7开始放到堆中),随着类的卸载而被销毁。
当启动程序时,主类以及其他相关类被加载到内存,相关的静态变量被分配内存,存放于方法区。在程序停止时类被卸载,所有变量被销毁(包括静态变量)。只要静态变量没有被销毁,也没有被置为null,那么该静态变量引用的对象就不会被GC回收。
总结:
1、ThreadLocal 并不解决线程间共享数据的问题
2、ThreadLocal 通过隐式的在不同线程内创建独立实例副本避免了实例线程安全的问题
3、每个线程持有一个Map并维护了ThreadLocal对象与具体实例的映射,该Map由于只被持有它的线程访问,故不存在线程安全以及锁的问题
4、ThreadLocalMap的Entry对ThreadLocal的引用为弱引用,避免了ThreadLocal对象无法被回收的问题
5、ThreadLocalMap的set方法通过调用replaceStaleEntry方法回收键为null的Entry对象的值(即保存的数据对象)以及Entry对象本身从而防止内存泄漏
6、ThreadLocal适用于变量在线程间隔离且在方法间共享的场景
3、简单使用
public class ThreadLocalUtil {
//private static final ThreadLocal<String> STRING_LOCAL = new ThreadLocal<>();
private static final ThreadLocal<String> STRING_LOCAL = ThreadLocal.withInitial(new Supplier<String>() {
@Override
public String get() {
return "";
}
});
public static void set(String value){
STRING_LOCAL.set(value);
}
public static String get(){
return STRING_LOCAL.get();
}
public static void remove(){
STRING_LOCAL.remove();
}
}
可以通过切面或者请求监听器在请求结束时将当前线程保存的ThreadLocal信息清除。
自定义监听器,实现ApplicationListener<T>接口(T为监听事件类型),注册为spring容器组件,交给spring容器管理。
package com.statics.proxy.others.listener;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
import org.springframework.web.context.support.ServletRequestHandledEvent;
@Component
public class MyListener implements ApplicationListener<ServletRequestHandledEvent> {
@Override
public void onApplicationEvent(ServletRequestHandledEvent event) {
ThreadLocalUtil.remove();
System.out.println(String.format("清除当前线程信息,uri = [%s],method = [%s],servletName = [%s],clientAddress = [%s]",
event.getRequestUrl(), event.getMethod(), event.getServletName(), event.getClientAddress()));
}
}
这个自定义监听器监听ServletRequestHandledEvent事件,该事件为请求结束回调事件,即一个请求完成结束后会执行onApplicationEvent方法内自定义的业务逻辑。
所有可使用的监听事件类型均继承自org.springframework.context.ApplicationEvent类。