一、 ThreadLocal
概述
ThreadLocal
是JDK 提供的,它提供了单个线程访问的本地变量。当多个线程操作这个变量时,实际操作的是自己本地内存里面的变量,从而避免了线程安全问题。创建一个 ThreadLocal
变量后,每个线程都会复制一个变量到自己的本地内存。
使用示例
如下示例中,开启了两个线程,都设置了 ThreadLocal
的本地变量值,然后调用 print()
打印当前本地变量。如果打印后调用了本地变量的 remove
方法,则删除本地内存中的该变量。
public class ThreadLocalTest {
static ThreadLocal<String> localVariables = new ThreadLocal<String>();
static void print() {
System.out.println(Thread.currentThread().getName() + " 获取的值为:" + localVariables.get());
localVariables.remove();
}
public static void main(String[] args) {
Thread first_thread = new Thread(new Runnable() {
public void run() {
localVariables.set("first thread's local variable");
print();
System.out.println("first thread remove after:" + localVariables.get());
}
});
first_thread.setName("first thread ");
Thread second_thread = new Thread(new Runnable() {
public void run() {
localVariables.set("second thread's local variable");
print();
System.out.println("second thread remove after:" + localVariables.get());
}
});
second_thread.setName("second thread ");
first_thread.start();
second_thread.start();
}
}
// 控制台输出
first thread 获取的值为:first thread's local variable
second thread 获取的值为:second thread's local variable
first thread remove after:null
second thread remove after:null
从上面的示例可以看出,在同一个 localVariables
中设置的值,但可以通过线程区分开,互不影响的访问。那么就看看 ThreadLocal
的原理是如何实现的。
二、 ThreadLocal
实现原理
1. java.lang.ThreadLocal#set
public void set(T value) {
// 获取当前线程
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t); //(1) 获取map
if (map != null)
map.set(this, value);
else
createMap(t, value); //(2)创建Map
}
1)获取 map :java.lang.ThreadLocal#getMap
ThreadLocalMap getMap(Thread t) {
return t.threadLocals; // Thread 类中的属性: ThreadLocal.ThreadLocalMap threadLocals
}
查看 ThreadLocal.ThreadLocalMap
,类图如下(就暂且认为是一个 Map
):
2) 创建 map: java.lang.ThreadLocal#createMap
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
它创建当前线程的 threadLocals
变量。
2. java.lang.ThreadLocal#get
public T get() {
// 获取当前线程
Thread t = Thread.currentThread();
//获取当前线程的 threadLocals 变量
ThreadLocalMap map = getMap(t);//(1)
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
T result = (T)e.value;
return result;
}
}
//如果 threadLocals 为空,则初始化当前线程的 threadLocals 成员变量
return setInitialValue();
}
1) java.lang.ThreadLocal#getMap
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
2) java.lang.ThreadLocal#setInitialValue
private T setInitialValue() {
T value = initialValue();// 初始化值,实际返回null
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
//java.lang.ThreadLocal#initialValue
protected T initialValue() {
return null;
}
3. java.lang.ThreadLocal#remove
public void remove() {
// 如果当前线程的 threadLocals 变量不为空,则删除当前线程中指定 ThreadLocal 实例的本地变量
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
4. ThreadLocal
总结
在每个线程内部都有一个名称为 threadLocals
的成员变量,该变量的类型为 HashMap
, 其中 key
为我们定义的 ThreadLocal
变量的 this
引用,value
则为我们使用 set
方法设置的值。每个线程的本地变量存放在线程自己的内存变量 threadLocals
中,如果当前线程一直不消亡,那么这些本地变量会一直存在,所以可能会造成内存溢出,因此使用完毕后要记得调用 ThreadLocal
的 remove
方法删除对应线程的 threadLocals
中的本地变量。
5、 ThreadLocal
不支持继承性
public class ThreadLocalTestShare {
public static ThreadLocal<String> threadLocal = new ThreadLocal<String>();
public static void main(String[] args) throws InterruptedException {
threadLocal.set("hello world");
Thread thread = new Thread(new Runnable() {
public void run() {
// 获取主线程设置的值
System.out.println("sub thread : "+threadLocal.get());
}
});
thread.start();
System.out.println("main: "+threadLocal.get());
}
}
// 控制台输出:
sub thread : null
main: hello world
如上代码所示,同一个 ThreadLocal
变量在父线程中设置值后,在子线程中获取不到。因为在子线程 thread
里面调用 get
方法时当前线程为 thread
,而调用 set
方法的线程是 main
线程,两者是不同的线程,自然子线程访问时返回 null
。那么有没有办法让子线程访问到父线程中的值?使用 InheritableThreadLocal
类就可以实现。
三、InheritableThreadLocal 类
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);
}
}
1. InheritableThreadLocal 实现的原理
如上代码可知,InheritableThreadLocal
继承 ThreadLocal
,并重写了3个方法。选中 childValue
查看引用的地方:
//java.lang.ThreadLocal.ThreadLocalMap#ThreadLocalMap(java.lang.ThreadLocal.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) {
// 在这里调用了 childValue()
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++;
}
}
}
}
//继续查看 ThreadLocalMap() 构造使用过的地方:
//java.lang.ThreadLocal#createInheritedMap
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
return new ThreadLocalMap(parentMap);
}
// 继续查看 createInheritedMap 使用过的地方:
//java.lang.Thread#init(java.lang.ThreadGroup, java.lang.Runnable, java.lang.String, long, java.security.AccessControlContext, boolean)
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name;
// 获取当前线程:
Thread parent = currentThread();
//....
// 如果父线程的 inheritableThreadLocals 变量不为 null
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);// 这里调用 createInheritedMap
this.stackSize = stackSize;
tid = nextThreadID();
}
回顾整个调用的过程,线程在创建时,在构造函数里面会调用 init
方法,然后会判断 main
函数所在的线程里的 inheritableThreadLocals 属性是否为 null
,又由于在 java.lang.InheritableThreadLocal#getMap(Thread t) 和 java.lang.InheritableThreadLocal#createMap(Thread t, T firstValue) 中都有在使用 inheritableThreadLocals 属性,因此这里 inheritableThreadLocals
不为 null
,在 createInheritedMap
内部使用 parent
的 inheritableThreadLocals 变量作为构造函数创建了一个新的 ThreadLocalMap
变量,然后赋给了子线程的 inheritableThreadLocals
变量。
总结:
InheritableThreadLocal
类通过重写了getMap
和createMap
让本地变量保存到了具体线程的 inheritableThreadLocals 变量里面,那么线程在通过InheritableThreadLocal
类实例的set
和get
方法设置变量时,就会创建当前线程的 inheritableThreadLocals 变量。当父线程创建子线程时,构造函数会把父线程中 inheritableThreadLocals 变量里面的本地变量复制一份保存到子线程的 inheritableThreadLocals 变量里面。
2. InheritableThreadLocal 的使用场景
1)子线程需要使用存放在 threadLocal
变量中的用户登录信息
2) 比如一些中间件需要把统一的 id 追踪的整个调用链路记录下来。