基本测试使用代码
public class ThreadLoaclDemo {
static ThreadLocal threadLocal = new ThreadLocal<>();
static String test = "hello";
public static void main(String[] args) {
new Thread(() ->{
threadLocal.set("hello threadLocal1");
test += "1";
System.out.println(threadLocal.get());
System.out.println(test);
}).start();
new Thread(() ->{
threadLocal.set("hello threadLocal2");
test += "2";
//threadLocal.remove(); 清除当前线程的本地内存中的 localVariable
System.out.println(threadLocal.get());
System.out.println(test);
}).start();
}
}
我们通常引出 ThradLocal,是考虑了这样的场景
在多线程处理共享变量时,都会存在着安全问题,,为了保证安全,通常会进行适当的同步操作
而同步的一般措施是:加锁
锁在保证着安全的同时,也会存在相关的效率和并发问题
那有没这样的变量,线程拿到后都是使用的自己的呢?
顺着这样的想法,很容易让人觉得,我们使用ThreadLocal,那么线程的本地变量就是存放在 ThreadLocal实例中,每个线程访问自己的变量,就是去 ThreadLocal通过当前线程实例拿到数据。
其实不是
线程的本地变量是存放在当前线程(调用线程)的 threadlocals中
通过源码,我们发现,Thread 类中有两个变量
ThreadLocal.ThreadLocalMap threadLocals = null; ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
默认情况下是为 null的,但是会在线程第一次调用 ThradLocal类的set或者get方法时,才会创建
先分析 set 、get、 remove方法
public T get() {
// 获取当前线程
Thread t = Thread.currentThread();
// 拿到当前用户线程的 threadLocalMap threadLocals
ThreadLocalMap map = getMap(t);
// 如果 threadLocals 不为空
if (map != null) {
// 从threadLocals中拿到当前线程本地变量中的值
// key 是 当前 ThreadLocal 实例
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
// threadLocals 为空,则初始化当前线程的threadLocals成员变量
return setInitialValue();
}
–
private T setInitialValue() {
// 初始化为 null
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
map.set(this, value);
} else {
createMap(t, value);
}
return value;
}
通过源码发现,首先是获取当前线程,在用当前线程去调用getMap方法,去获取当前线程自己的变量(threadlocal),其被绑定到了线程的成员变量上
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
再看set,源码也类似
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
// 424 行
// 将值存入 threadlocals 中
map.set(this, value);
} else {
createMap(t, value);
}
}
remove方法就是将 当前线程的 threadlocals 中的 ThreadLocal实例移除
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null) {
m.remove(this);
}
}
通过这三个方法可以知道,其实 ThreadLocal 充当着的是一个工具类的作用
其并没有去过多的操控本类中的元素
而是变相的去使用当前线程中的 threadlocals
他调用 set 方法,将value 存入 调用线程的threadlocals中,调用 get方法取出来
如果调用线程一直不终止,那么存在threadlocals中的变量一直不会移除,这样就会有内存溢出的可能,所以适用后需要调用 ThreadLocal中的remove方法删除对应线程的 threadlocals中的本地变量
为何是map结构呢?
因为每个线程不止一个 ThreadLocal 变量
ThreadLocal也有一个短板,那就是 父子线程中无法使用 ThreadLocal共享
觉得这也不是 ThreadLocal 的短板,因为ThreadLocal,操作都是建立在当前线程
那么 InheritableThreadLocal因运而生
源码中就只有三个方法,也就是 重写的ThreadLocal的三个方法
public class InheritableThreadLocalextends ThreadLocal { protected T childValue(T parentValue) { return parentValue; } ThreadLocalMap getMap(Thread t) { return t.inheritableThreadLocals; } void createMap(Thread t, T firstValue) { t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue); } }
要想了解 InheritableThreadLocal 是如何让子线程访问父线程变量时,需要从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) {
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name;
// 获取当前线程
Thread parent = currentThread();
if (inheritThreadLocals && parent.inheritableThreadLocals != null) {
// 如果父线程的 inheritableThreadLocals 不为空
// 那就设置子线程中的 inheritableThreadLocals 变量
// 使用父线程的 inheritableThreadLocals 作为构造函数,创建一个新的 ThreadLocalMap 变量,赋值给子线程的 inheritableThreadLocals
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
}
this.stackSize = stackSize;
tid = nextThreadID();
}
追踪 createInheritedMap
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
return new ThreadLocalMap(parentMap);
}
再进ThreadLocalMap看看是怎么通过父线程的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
这下就知道了
当父线程创建子线程时,构造函数会把父线程中的 inheritableThreadLocals 的本地变量复制一份,保存在子线程中的 inheritableThreadLocals中
inheritableThreadLocals 的使用场景
子线程需要使用父线程中存放的用户登录信息
中间件需要把统一的id追踪的整个调用链路记录
《java并发编程之美》



