前言
我们都知道,在多线程环境下访问同一个共享变量,可能会出现线程安全的问题,为了保证线程安全,我们往往会在访问这个共享变量的时候加锁,以达到同步的效果,如下图所示。
对共享变量加锁虽然能够保证线程的安全,但是却增加了开发人员对锁的使用技能,如果锁使用不当,则会导致死锁的问题。而 ThreadLocal 能够做到在创建变量后,每个线程对变量访问时访问的是线程自己的本地变量。
什么是 ThreadLocal?
ThreadLocal 是 JDK 提供的,支持线程本地变量。也就是说,如果我们创建了一个 ThreadLocal 变量,则访问这个变量的每个线程都会有这个变量的一个本地副本。如果多个线程同时对这个变量进行读写操作时,实际上操作的是线程自己本地内存中的变量,从而避免了线程安全的问题。
ThreadLocal 使用示例
例如,我们使用 ThreadLocal 保存并打印相关的变量信息,程序如下所示。
public class ThreadLocalTest {
private static ThreadLocal<String> threadLocal = new ThreadLocal<String>();
public static void main(String[] args){
//创建第一个线程
Thread threadA = new Thread(()->{
threadLocal.set("ThreadA:" + Thread.currentThread().getName());
System.out.println("线程A本地变量中的值为:" + threadLocal.get());
});
//创建第二个线程
Thread threadB = new Thread(()->{
threadLocal.set("ThreadB:" + Thread.currentThread().getName());
System.out.println("线程B本地变量中的值为:" + threadLocal.get());
});
//启动线程A和线程B
threadA.start();
threadB.start();
}
}
复制代码
运行程序,打印的结果信息如下所示。
线程A本地变量中的值为:ThreadA:Thread-0
线程B本地变量中的值为:ThreadB:Thread-1
复制代码
此时,我们为线程 A 增加删除 ThreadLocal 中的变量的操作,如下所示。
public class ThreadLocalTest {
private static ThreadLocal<String> threadLocal = new ThreadLocal<String>();
public static void main(String[] args){
//创建第一个线程
Thread threadA = new Thread(()->{
threadLocal.set("ThreadA:" + Thread.currentThread().getName());
System.out.println("线程A本地变量中的值为:" + threadLocal.get());
threadLocal.remove();
System.out.println("线程A删除本地变量后ThreadLocal中的值为:" + threadLocal.get());
});
//创建第二个线程
Thread threadB = new Thread(()->{
threadLocal.set("ThreadB:" + Thread.currentThread().getName());
System.out.println("线程B本地变量中的值为:" + threadLocal.get());
System.out.println("线程B没有删除本地变量:" + threadLocal.get());
});
//启动线程A和线程B
threadA.start();
threadB.start();
}
}
复制代码
此时的运行结果如下所示。
线程A本地变量中的值为:ThreadA:Thread-0
线程B本地变量中的值为:ThreadB:Thread-1
线程B没有删除本地变量:ThreadB:Thread-1
线程A删除本地变量后ThreadLocal中的值为:null
复制代码
通过上述程序我们可以看出,线程 A 和线程 B 存储在 ThreadLocal 中的变量互不干扰,线程 A 存储的变量只能由线程 A 访问,线程 B 存储的变量只能由线程 B 访问。
ThreadLocal 原理
首先,我们看下 Thread 类的源码,如下所示。
public class Thread implements Runnable {
/***********省略N行代码*************/
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
/***********省略N行代码*************/
}
复制代码
由 Thread 类的源码可以看出,在 ThreadLocal 类中存在成员变量 threadLocals 和 inheritableThreadLocals,这两个成员变量都是 ThreadLocalMap 类型的变量,而且二者的初始值都为 null。只有当前线程第一次调用 ThreadLocal 的 set()方法或者 get()方法时才会实例化变量。
这里需要注意的是:每个线程的本地变量不是存放在 ThreadLocal 实例里面的,而是存放在调用线程的 threadLocals 变量里面的。也就是说,调用 ThreadLocal 的 set()方法存储的本地变量是存放在具体线程的内存空间中的,而 ThreadLocal 类只是提供了 set()和 get()方法来存储和读取本地变量的值,当调用 ThreadLocal 类的 set()方法时,把要存储的值放入调用线程的 threadLocals 中存储起来,当调用 ThreadLocal 类的 get()方法时,从当前线程的 threadLocals 变量中将存储的值取出来。
接下来,我们分析下 ThreadLocal 类的 set()、get()和 remove()方法的实现逻辑。
set()方法
set()方法的源代码如下所示。
public void set(T value) {
//获取当前线程
Thread t = Thread.currentThread();
//以当前线程为Key,获取ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
//获取的ThreadLocalMap对象不为空
if (map != null)
//设置value的值
map.set(this, value);
else
//获取的ThreadLocalMap对象为空,创建Thread类中的threadLocals变量
createMap(t, value);
}
复制代码
在 set()方法中,首先获取调用 set()方法的线程,接下来,使用当前线程作为 Key 调用 getMap(t)方法来获取 ThreadLocalMap 对象,getMap(Thread t)的方法源码如下所示。
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
复制代码
可以看到,getMap(Thread t)方法获取的是线程变量自身的 threadLocals 成员变量。
在 set()方法中,如果调用 getMap(t)方法返回的对象不为空,则把 value 值设置到 Thread 类的 threadLocals 成员变量中,而传递的 key 为当前 ThreadLocal 的 this 对象,value 就是通过 set()方法传递的值。
如果调用 getMap(t)方法返回的对象为空,则程序调用 createMap(t, value)方法来实例化 Thread 类的 threadLocals 成员变量。
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
复制代码
也就是创建当前线程的 threadLocals 变量。
get()方法
get()方法的源代码如下所示。
public T get() {
//获取当前线程
Thread t = Thread.currentThread();
//获取当前线程的threadLocals成员变量
ThreadLocalMap map = getMap(t);
//获取的threadLocals变量不为空
if (map != null) {
//返回本地变量对应的值
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
//初始化threadLocals成员变量的值
return setInitialValue();
}
复制代码
通过当前线程来获取 threadLocals 成员变量,如果 threadLocals 成员变量不为空,则直接返回当前线程绑定的本地变量,否则调用 setInitialValue()方法初始化 threadLocals 成员变量的值。
private T setInitialValue() {
//调用初始化Value的方法
T value = initialValue();
Thread t = Thread.currentThread();
//根据当前线程获取threadLocals成员变量
ThreadLocalMap map = getMap(t);
if (map != null)
//threadLocals不为空,则设置value值
map.set(this, value);
else
//threadLocals为空,创建threadLocals变量
createMap(t, value);
return value;
}
复制代码
其中,initialValue()方法的源码如下所示。
protected T initialValue() {
return null;
}
复制代码
通过 initialValue()方法的源码可以看出,这个方法可以由子类覆写,在 ThreadLocal 类中,这个方法直接返回 null。
remove()方法
remove()方法的源代码如下所示。
public void remove() {
//根据当前线程获取threadLocals成员变量
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
//threadLocals成员变量不为空,则移除value值
m.remove(this);
}
复制代码
remove()方法的实现比较简单,首先根据当前线程获取 threadLocals 成员变量,不为空,则直接移除 value 的值。
注意:如果调用线程一致不终止,则本地变量会一直存放在调用线程的 threadLocals 成员变量中,所以,如果不需要使用本地变量时,可以通过调用 ThreadLocal 的 remove()方法,将本地变量从当前线程的 threadLocals 成员变量中删除,以免出现内存溢出的问题。
ThreadLocal 变量不具有传递性
使用 ThreadLocal 存储本地变量不具有传递性,也就是说,同一个 ThreadLocal 在父线程中设置值后,在子线程中是无法获取到这个值的,这个现象说明 ThreadLocal 中存储的本地变量不具有传递性。
接下来,我们来看一段代码,如下所示。
public class ThreadLocalTest {
private static ThreadLocal<String> threadLocal = new ThreadLocal<String>();
public static void main(String[] args){
//在主线程中设置值
threadLocal.set("ThreadLocalTest");
//在子线程中获取值
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("子线程获取值:" + threadLocal.get());
}
});
//启动子线程
thread.start();
//在主线程中获取值
System.out.println("主线程获取值:" + threadLocal.get());
}
}
复制代码
运行这段代码输出的结果信息如下所示。
主线程获取值:ThreadLocalTest
子线程获取值:null
复制代码
通过上述程序,我们可以看出在主线程中向 ThreadLocal 设置值后,在子线程中是无法获取到这个值的。那有没有办法在子线程中获取到主线程设置的值呢?此时,我们可以使用 InheritableThreadLocal 来解决这个问题。
InheritableThreadLocal 使用示例
InheritableThreadLocal 类继承自 ThreadLocal 类,它能够让子线程访问到在父线程中设置的本地变量的值,例如,我们将 ThreadLocalTest 类中的 threadLocal 静态变量改写成 InheritableThreadLocal 类的实例,如下所示。
public class ThreadLocalTest {
private static ThreadLocal<String> threadLocal = new InheritableThreadLocal<String>();
public static void main(String[] args){
//在主线程中设置值
threadLocal.set("ThreadLocalTest");
//在子线程中获取值
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("子线程获取值:" + threadLocal.get());
}
});
//启动子线程
thread.start();
//在主线程中获取值
System.out.println("主线程获取值:" + threadLocal.get());
}
}
复制代码
此时,运行程序输出的结果信息如下所示。
主线程获取值:ThreadLocalTest
子线程获取值:ThreadLocalTest
复制代码
可以看到,使用 InheritableThreadLocal 类存储本地变量时,子线程能够获取到父线程中设置的本地变量。
InheritableThreadLocal 原理
首先,我们来看下 InheritableThreadLocal 类的源码,如下所示。
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
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 类的源代码可知,InheritableThreadLocal 类继承自 ThreadLocal 类,并且重写了 ThreadLocal 类的 childValue()方法、getMap()方法和 createMap()方法。也就是说,当调用 ThreadLocal 的 set()方法时,创建的是当前 Thread 线程的 inheritableThreadLocals 成员变量而不再是 threadLocals 成员变量。
这里,我们需要思考一个问题:InheritableThreadLocal 类的 childValue()方法是何时被调用的呢? 这就需要我们来看下 Thread 类的构造方法了,如下所示。
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
Thread(Runnable target, AccessControlContext acc) {
init(null, target, "Thread-" + nextThreadNum(), 0, acc, false);
}
public Thread(ThreadGroup group, Runnable target) {
init(group, target, "Thread-" + nextThreadNum(), 0);
}
public Thread(String name) {
init(null, null, name, 0);
}
public Thread(ThreadGroup group, String name) {
init(group, null, name, 0);
}
public Thread(Runnable target, String name) {
init(null, target, name, 0);
}
public Thread(ThreadGroup group, Runnable target, String name) {
init(group, target, name, 0);
}
public Thread(ThreadGroup group, Runnable target, String name,
long stackSize) {
init(group, target, name, stackSize);
}
复制代码
可以看到,Thread 类的构造方法最终调用的是 init()方法,那我们就来看下 init()方法,如下所示。
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
/************省略部分源码************/
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
/* Stash the specified stack size in case the VM cares */
this.stackSize = stackSize;
/* Set thread ID */
tid = nextThreadID();
}
复制代码
可以看到,在 init()方法中会判断传递的 inheritThreadLocals 变量是否为 true,同时父线程中的 inheritableThreadLocals 是否为 null,如果传递的 inheritThreadLocals 变量为 true,同时,父线程中的 inheritableThreadLocals 不为 null,则调用 ThreadLocal 类的 createInheritedMap()方法。
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
return new ThreadLocalMap(parentMap);
}
复制代码
在 createInheritedMap()中,使用父线程的 inheritableThreadLocals 变量作为参数创建新的 ThreadLocalMap 对象。然后在 Thread 类的 init()方法中会将这个 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) {
//调用重写的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 的构造函数中,调用了 InheritableThreadLocal 类重写的 childValue()方法。而 InheritableThreadLocal 类通过重写 getMap()方法和 createMap()方法,让本地变量保存到了 Thread 线程的 inheritableThreadLocals 变量中,线程通过 InheritableThreadLocal 类的 set()方法和 get()方法设置变量时,就会创建当前线程的 inheritableThreadLocals 变量。此时,如果父线程创建子线程,在 Thread 类的构造函数中会把父线程中的 inheritableThreadLocals 变量里面的本地变量复制一份保存到子线程的 inheritableThreadLocals 变量中。
如果觉得文章对你有点帮助,请微信搜索并关注「 冰河技术 」微信公众号,跟冰河学习高并发编程技术。
最后,附上并发编程需要掌握的核心技能知识图,祝大家在学习并发编程时,少走弯路。
评论 (1 条评论)