开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第13天,点击查看活动详情
前言
我们经常听说ThreadLocal可以解决线程安全的问题,但是却不知道它是如何做到的,为了了解一下ThreadLocal的设计思路,我们今天这篇文章通过源码来看一下它是如何实现线程安全的;
使用示例
ThreadLocal的使用特别简单,通过下面这一段代码,开发人员就可以在项目当中使用set()、get()、remove()方法了:
private static ThreadLocal<String> THREAD_LOCAL = new ThreadLocal<>();
public static void set(String data) {
THREAD_LOCAL.set(data);
}
public static String get() {
return THREAD_LOCAL.get();
}
public static void remove() {
THREAD_LOCAL.remove();
}
它经常被用来保存上下文,比如spring中使用它来保存事务的上下文,seata中用ThreadLocal来保存全局分布式事务的XID等等;
源码解析
为了了解ThreadLocal是如何做到线程安全的,我们需要去看一下它的源码,先来看一下set()方法:
public void set(T value) {
// 获取当前线程
Thread t = Thread.currentThread();
// 从线程中取出ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null) {
// 把ThreadLocal作为key,把value放进ThreadLocalMap中
map.set(this, value);
} else {
// 创建一个ThreadLocalMap放进当前线程中
createMap(t, value);
}
}
通过上面的代码,我们就明白了,原理ThreadLocal是通过给每一个线程里面的ThreadLocalMap都放一份数据来保证线程安全的;
通过上图我们可以很明显地知道,每个线程中都有属于自己的value值,虽然它们都共用同一个ThreadLocal实例作为key;
我们再来看一下get()方法:
public T get() {
// 获取当前线程
Thread t = Thread.currentThread();
// 取出线程中的ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null) {
// 通过ThreadLocal实例作为key取出里面的元素
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
// 取出value并返回
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
// 如果map为null,那么就执行初始化操作
return setInitialValue();
}
这个和set()方法如出一辙,因为是通过TheadLocal作为key存进去的,所以取数据的时候也需要通过ThreadLocal作为key来取数据;ThreadLocal还提供给我们initialValue()方法让我们在没有数据的时候可以初始化数据,所以如果map为null的情况下,还可以实现自定义初始化数据;
下面我们再来看看remove()方法:
public void remove() {
// 获取当前线程中的ThreadLocalMap
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null) {
// map不为null,异常对应的数据
m.remove(this);
}
}
这里特别需要注意的就是,在线程的业务逻辑处理完毕后,一定要记得执行remove()方法把数据删掉;如果该线程是线程池中的线程,存在复用的情况下,会导致上下文污染的情况,更严重的可能会造成程序的OOM;