文章目录
1. 概述
ThreadLocal提供了线程本地变量,当创建一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个本地副本。当多个线程操作这个变量时,实际操作的是自己本地内存里面的变量,从而避免了线程安全问题。
1.1使用示例
public class ThreadLocalSimple {
//(1)print函数
static void print(String str) {
//1.1 打印当前线程本地内存中localVariable变量的值
System.out.println(str + ":"+localVariable.get());
//1.2 清除当前线程本地内存中的localVariable变量
localVariable.remove() ;
}
//(2)创建ThreadLocal变量
static ThreadLocal<String> localVariable = new ThreadLocal<> () ;
public static void main(String[] args) {
//创建线程
Thread threadOne =new Thread(new Runnable() {
@Override
public void run() {
//3.1 设置线程One中本地变量 Variable的值
localVariable.set ("threadOne local variable");
//3.2 调用打印函数
print ("threadOne" ) ;
//3.3 打印本地变量值
System.out.println ("threadOne remove after" + " :" +localVariable.get()) ;
}
});
//创建线程
Thread threadTwo =new Thread(new Runnable() {
@Override
public void run() {
//4.1 设置线程two中本地变量 Variable的值
localVariable.set ("threadTwo local variable");
//4.2 调用打印函数
print ("threadTwo" ) ;
//4.3 打印本地变量值
System.out.println ("threadTwo remove after" + " :" +localVariable.get()) ;
}
});
// ( 5 )启动线程
threadOne.start() ;
threadTwo.start() ;
}
}
运行结果如下:
threadTwo:threadTwo local variable
threadOne:threadOne local variable
threadTwo remove after :null
threadOne remove after :null
2. 原理
ThreadLocal相关类的类图结构:
由上图可知,Thread 类中有一个threadLocals 和一个inheritableThreadLocals,它们都是 ThreadLocalMap 类型的变量,而ThreadLocalMap 是一个定制化的Hashmap。在默认情况下,每个线程中的这两个变量都为null,只有当前线程第一次调用ThreadLocal的 set 或者 get 方法时才会创建它们。每个线程的本地变量存放在调用线程的ThreadLocals变量里面。ThreadLocal就是一个工具壳,它通过set方法把value值放入调用线程的threadLocals里面并存放起来,当调用线程调用它的get方法时,再从当前线程的threadLocals 变量里面将其拿出来使用。如果调用线程一直不终止,那么这个本地变量会一直存放在调用线程的threadLocals 变量里面,可能会造成内存溢出。所以当不需要使用本地变量时可以通过调用ThreadLocal变量的remove方法,从当前线程的threadLocals里面删除该本地变量,另外每个线程可以关联多个 ThreadLocal 变量。
ThreadLocalMap内部是一个Entry数组,Entry 继承自WeakReference,Entry内部的value 用来存放通过ThreadLocal 的set 方法传递的值,k 被传递给WeakReference 的构造函数,也就是说ThreadLocalMap 里面的key 为ThreadLocal 对象的弱引用, 具体就是referent 变量引用了ThreadLocal 对象, value 为具体调用ThreadLocal 的set 方法时传递的值。
3.ThreadLocal 的set 、get 及remove方法
3.1 void set(T value)
public void set(T value) {
//(1)获取当前线程
Thread t = Thread.currentThread();
// (2)将当前线程作为key ,去查找对应的线程变量,找到则设置
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
//(3)第一次调用就创建当前线程对应的HashMap
createMap(t, value);
}
代码(1) 首先获取调用线程,然后使用当前线程作为参数调用getMap(t) 方法
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
getMap(t)的作用是获取线程自己的变量threadLocals, threadlocals 变量被绑定到了线程的成员变量上。
如果getMap(t)的返回值不为空,则把value 值设置到threadLocals 中,也就是把当前变量值放入当前线程的内存变量threadLocals 中。threadLocals 是一个HashMap 结构, 其中key 就是当前ThreadLocal 的实例对象引用, value 是通过set 方法传递的值。
如果getMap(t)返回空值则说明是第一次调用set 方法,这时创建当前线程的threadLocals 变量。
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
createMap创建当前线程的threadLocals 变量。
当一个线程调用 ThreadLocal 的 set方法设置变量时,当前线程的 ThreadLocalMap 里就会存放一个记录,这个记录的key为ThreadLocal的弱引用,value 则为设置的值。
3.2 T get()
public T get() {
//(4)获取当前线程
Thread t = Thread.currentThread();
//(5)获取当前线程的threadLocals变量
ThreadLocalMap map = getMap(t);
if (map != null) {
//(6)如果threadLocals不为null,则返回对应本地变量的值
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
//(7)threadLocals为空则初始化当前线程的threadLocals成员变量
return setInitialValue();
}
代码( 4 )首先获取当前线程实例, 如果当前线程的threadLocals 变量不为null , 则直接返回当前线程绑定的本地变量, 否则执行代码( 7 )进行初始化。setinitialValue()的代码如下。
private T setInitialValue() {
//(8)初始化为null
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
//(9)如果当前线程的threadLocals变量不为空
if (map != null)
map.set(this, value);
else
//(10)如果当前线程的threadLocals 变量为空
createMap(t, value);
return value;
}
protected T initialValue() {
return null;
}
如果当前线程的threadLocals 变量不为空, 则设置当前线程的本地变量值为null , 否则调用createMap 方法创建当前线程的createMap 变量。
3.3 void remove()
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
如以上代码所示, 如果当前线程的threadLocals 变量不为空, 则删除当前线程中指定ThreadLocal 实例的本地变量。
4. ThreadLocal不支持继承性
首先看一个例子
public class TestThreadLocal {
// 创建线程变量,Threadlocal 不支持继承性
public static ThreadLocal<String> threadLocal = new ThreadLocal<String> ();
public static void main(String [] args) {
// (2) 设置线程交量
threadLocal.set ("hello world");
// (3 )启动子线程
Thread thread= new Thread (new Runnable() {
public void run () {
// ( 4 ) 子线程输出线程交量的值
System.out.println ( "thread :" + threadLocal.get());
}
}) ;
thread.start();
// (5 )主线程输出线程变量的值
System.out.println( "main :" +threadLocal.get()) ;
}
}
输出结果如下:
main :hello world
thread :null
也就是说,同一个 ThreadLocal 变量在父线程中被设置值后,在子线程中是获取不到的。因为在子线程thread 里面调用get方法时当前线程为 thread 线程,而这里调用 set方法设置线程变量的是 main 线程,两者是不同的线程,自然子线程访问时返回null。可以通过InheritableThreadLocal让子线程访问父线程中的值。
5. InheritableThreadLocal支持继承性
InheritableThreadLocal继承自ThreadLocal,其提供了一个特性,就是让子线程可以访问在父线程中设置的本地变量。
public class TestThreadLocal {
// InheritableThreadLocal支持继承性
public static ThreadLocal<String> threadLocal = new InheritableThreadLocal<String> ();
public static void main(String [] args) {
// (2) 设置线程交量
threadLocal.set ("hello world");
// (3 )启动子线程
Thread thread= new Thread (new Runnable() {
public void run () {
// ( 4 ) 子线程输出线程交量的值
System.out.println ( "thread :" + threadLocal.get());
}
}) ;
thread.start();
// (5 )主线程输出线程变量的值
System.out.println( "main :" +threadLocal.get()) ;
}
}
运行结果如下:
main :hello world
thread :hello world
可见,现在可以从子线程正常获取到线程变量的值了。
5.1 代码解析
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
//(1)
protected T childValue(T parentValue) {
return parentValue;
}
//(2)
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}
//(3)
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
}
由如上代码可知, InheritabIeThreadLocal 继承了ThreadLocal ,并重写了三个方法。由代码(3)可知, InheritableThreadLocal 重写了createMap 方法, 那么现在当第一次调用set 方法时,创建的是当前线程的inheritableThreadLocals 变量的实例而不再是threadLocals 。由代码(2)可知,当调用get 方法获取当前线程内部的map 变量时, 获取的是inheritableThreadLocals 而不再是threadLocals。
综上可知,在InheritableThreadLocal 的世界里, 变量inheritableThreadLocals 替代了threadLocals。
下面我们看一下重写的代码(1)何时执行, 以及如何让子线程可以访问父线程的本地变量。这要从创建Thread 的代码说起,打开Thread 类的默认构造函数,代码如下。
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
private void init(ThreadGroup g, Runnable target, String name,
long stackSize) {
init(g, target, name, stackSize, null, true);
}
private void init(ThreadGroup g, Runnable target, String name,long stackSize, AccessControlContext acc, boolean inheritThreadLocals) {
...
//(4)获取当前线程
Thread parent = currentThread();
...
//(5))如果父线程的inheritableThreadLocals 变量不为null
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
//(6)设置子线程中的inheritableThreadLocals 变量
this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
this.stackSize = stackSize;
tid = nextThreadID();
}
如上代码在创建线程时,在构造函数里面会调用init 方法。代码(4)获取了当前线程(这里是指main 函数所在的线程,也就是父线程〉, 然后代码( 5 )判断传递的inheritThreadLocals 变量是否为true,同时父线程(main 函数)所在线程里面的inheritableThreadLocals 属性是否为null ,前面我们讲了InheritableThreadLocal 类的get 和set 方法操作的是inheritableThreadLocals ,所以这里的inheritableThreadLocal 变量不为null , 因此会执行代码(6)。下面看一下createlnheritedMap 的代码。
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
return new ThreadLocalMap(parentMap);
}
可以看到,在createInheritedMap内部使用父线程的inheritableThreadLocals 变量作为构造函数创建了一个新的ThreadLocalMap 变量, 然后赋值给了子线程的inheritableThreadLocals 变量。下面我们看看在ThreadLocalMap 的构造函数内部都做了什么事情。
private ThreadLocalMap(ThreadLocalMap parentMap) {
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
table = new Entry[len];
for (int j = 0; j < len; j++) {
Entry e = parentTable[j];
if (e != null) {
@SuppressWarnings("unchecked")
ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
if (key != null) {
//(7)调用重写的方法
Object value = key.childValue(e.value);
Entry c = new Entry(key, value);
int h = key.threadLocalHashCode & (len - 1);
while (table[h] != null)
h = nextIndex(h, len);
table[h] = c;
size++;
}
}
}
}
在该构造函数内部把父线程的inheritableThreadLocals成员变量的值复制到新的ThreadLocalMap对象中,其中代码(7)调用了InheritableThreadLocal 类重写的代码(1)。
总结: InheritableThreadLocal类通过重写代码(2)和(3)让本地变量保存到了具体线程的inheritableThreadLocals变量里面,那么线程在通过 InheritableThreadLocal 类实例的 set 或者 get方法设置变量时,就会创建当前线程的inheritableThreadLocals 变量。当父线程创建子线程时,构造函数会把父线程中inheritableThreadLocals 变量里面的本地变量复制一份保存到子线程的inheritableThreadLocals 变量里面。
需要子线程可以获取父线程的threadlocal 变量的场景:比如子线程需要使用存放在threadlocal 变量中的用户登录信息,再比如一些中间件需要把统一的id 追踪的整个调用链路记录下来。
其实子线程使用父线程中的threadlocal 方法有多种方式, 比如创建线程时传入父线程中的变量,并将其复制到子线程中,或者在父线程中构造一个map 作为参数传递给子线程,但是这些都改变了我们的使用习惯,所以在这些情况下InheritabIeThreadLocal 就显得比较有用。
6.内存泄漏
如果一个线程一直存在且没有调用ThreadLocal的remove方法,并且这时候在其他地方还有对ThreadLocal的引用,则当前线程的ThreadLocalMap变量里面会存在对 ThreadLocal 变量的引用和对 value 对象的引用,它们是不会被释放的,这就会造成内存泄漏。
考虑这个ThreadLocal变量没有其他强依赖,而当前线程还存在的情况,由于线程的ThreadLocalMap里面的key是弱依赖,所以当前线程的ThreadLocalMap里面的ThreadLocal变量的弱引用会在gc的时候被回收,但是对应的 value 还是会造成内存泄漏,因为这时候 ThreadLocalMap 里面就会存在 key 为 null 但是 value 不为 null 的 entry 项。
其实在 ThreadLocal的 set、get和remove 方法里面可以找一些时机对这些 key 为 nul的 entry进行清理,但是这些清理不是必须发生的。下面简单说下ThreadLocalMap 的remove 方法中的清理过程。
private void remove(ThreadLocal<?> key) {
//(1)计算当前ThreadLocal变量所在的table数组位置,尝试使用快速定位方法
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
//(2)这里使用循环是防止快速定位失效后,遍历table数组
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
//(3)找到
if (e.get() == key) {
//(4)找到则调用WeakReference的clear方法清除对ThreadLocal的弱引用
e.clear();
//(5)清理key为nu11的元素
expungeStaleEntry(i);
return;
}
}
}
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// (6)去掉对value的引用
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
// Rehash until we encounter null
Entry e;
int i;
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
//(7)如果key为null,则去掉对value的引用
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
int h = k.threadLocalHashCode & (len - 1);
if (h != i) {
tab[i] = null;
// Unlike Knuth 6.4 Algorithm R, we must scan until
// null because multiple entries could have been stale.
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}
代码(4)调用了Entry 的clear 方法,实际调用的是父类WeakReference 的clear 方法,作用是去掉对ThreadLocal 的弱引用。
如下代码(6)去掉对value 的引用, 到这里当前线程里面的当前ThreadLocal 对象的信息被清理完毕了。
代码(7) 从当前元素的下标开始查看table 数组里面是否有key 为null 的其他元素,有则清理。循环退出的条件是遇到table 里面有null 的元素。所以这里知道null 元素后面的Entry 里面key 为null 的元素不会被清理。
总结: ThreadLocalMap 的Entry 中的key 使用的是对ThreadLocal 对象的弱引用,这在避免内存泄漏方面是一个进步,因为如果是强引用, 即使其他地方没有对ThreadLocal 对象的引用,ThreadLocalMap 中的ThreadLocal 对象还是不会被回收, 而如果是弱引用则ThreadLocal 引用是会被回收掉的。但是对应的value 还是不能被回收,这时候ThreadLocalMap 里面就会存在key 为null 但是value 不为null 的entry 项, 虽然ThreadLocalMap 提供了set 、get 和remove 方法, 可以在一些时机下对这些Entry 项进行清理,但是这是不及时的, 也不是每次都会执行,所以在一些情况下还是会发生内存漏, 因此在使用完毕后及时调用remove 方法才是解决内存泄漏问题的王道。
如果在线程池里面设置了ThreadLocal 变量,则一定要记得及时清理,因为线程池里面的核心线程是一直存在的,如果不清理,线程池的核心线程的threadLocals 变量会一直持有ThreadLocal 变量。