ThreadLocal到底是什么
- 案例演示
我们发现所有线程都是操作的同一个变量,现在有一个需求我们想要这些线程操作的变量是一个确切的值,所以我们引入 ThreadLocal 类
- 案例演示
ThreadLocal原理分析
ThreadLoal 变量,它的基本原理是,同一个 ThreadLocal 所包含的对象(对ThreadLocal而言即为 Integer 类型变量),在不同的 Thread 中有不同的副本(实际上是不同的实例):
- 因为每个 Thread 内有自己的实例副本,且该副本只能由当前 Thread 使用。
- 既然其它 Thread 不可访问,那就不存在多线程间共享的问题。
ThreadLocal 提供了线程本地的实例。它与普通变量的区别在于,每个使用该变量的线程都会初始化一个完全独立的实例副本。ThreadLocal 变量通常被private static修饰。当一个线程结束时,它所使用的所有 ThreadLocal 相对的实例副本都会被回收。
因此ThreadLocal 非常适用于这样的场景:每个线程需要自己独立的实例且该实例需要在多个方法中使用。
ThreadLocal源码分析
分析源码首先要找到入口,我们就以 threadLocal.get(); 方法作为切入点。
- ThreadLocal 是如何存储每个线程的实例
- ThreadLocal 是什么时候进行初始化的
public T get() {
//拿到当前线程
Thread t = Thread.currentThread();
//拿到 ThreadLocalMap 属性的对象,看名字和 Map 有关系
ThreadLocalMap map = getMap(t);
//判断拿到的对象是否为空
if (map != null) {
//从 Map 中拿到当前 key 为当前对象的 Entry
ThreadLocalMap.Entry e = map.getEntry(this);
//如果不等于 null,拿到他的value并返回
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
//如果为空则调用 setInitialValue() 方法
return setInitialValue();
}
- 如果这个线程第一次调用getMap()方法,返回值一定为null
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
//初始值为null
ThreadLocal.ThreadLocalMap threadLocals = null;
- 所以第一次一定会调用setInitialValue()方法
private T setInitialValue() {
//调用重写的initialValue()方法
T value = initialValue();
//拿到当前线程
Thread t = Thread.currentThread();
//再尝试获取一次
ThreadLocalMap map = getMap(t);
//如果获取到了,就更新value
if (map != null)
map.set(this, value);
//没获取到就创建一个Map
else
createMap(t, value);
//把拿到的值返回回去
return value;
}
- 先看一下initialValue()方法,他是一个空壳方法默认返回 null
protected T initialValue() {
return null;
}
我们在创建 ThreadLocal 的时候通过ThreadLocal.withInitial(() -> { return 0; }); 来创建的
public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
return new SuppliedThreadLocal<>(supplier);
}
//我们可以看到 ThreadLocal 的静态内部类继承了 ThreadLocal 类,而且覆写了 initialValue 方法
static final class SuppliedThreadLocal<T> extends ThreadLocal<T> {
private final Supplier<? extends T> supplier;
SuppliedThreadLocal(Supplier<? extends T> supplier) {
this.supplier = Objects.requireNonNull(supplier);
}
@Override
protected T initialValue() {
return supplier.get();
}
}
- createMap(t, value);方法创建了 ThreadLocalMap 对象并且赋值给线程的 threadLocals 属性
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
- 刨根问底,再看一下 ThreadLocalMap 对象的组成
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
//创建一个 Entry 集合默认长度为16
table = new Entry[INITIAL_CAPACITY];
//这句话很重要,后面的set()方法再分析
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
//创建Entry节点放入table属性中
table[i] = new Entry(firstKey, firstValue);
//设置已存在的节点个数
size = 1;
//设置触发扩容的条件
setThreshold(INITIAL_CAPACITY);
}
- 接着看创建Entry节点
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
重点来了,Entry节点继承了 WeakReference 类,WeakReference这个类代表的弱引用,也就是说,如果这个 threadlocal 对象设为null,正常情况下是不会被GC的,因为他被threadLocalMap 引用了,但是他设置为了弱引用,那么就会忽略这个引用进行GC。
- 到此位置调用get()方法的流程分析完毕了,以图的形式来更好地理解
-
既然 ThreadLocalMap 是个集合那么就一定会有多个 ThreadLocal 对象
-
接着分析threadlocal.set()方法
private void set(ThreadLocal<?> key, Object value) {
//拿到当前线程的Entry集合
Entry[] tab = table;
//得到threadLocals的长度
int len = tab.length;
//简单理解为计算Hash值,但和普通的计算方法不一样
int i = key.threadLocalHashCode & (len-1);
//拿到Hash值对应的Entry对象
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
//更新value值
if (k == key) {
e.value = value;
return;
}
//如果 key 为null,也就是说 threadlocal 对象被设置为null,清空对应的Entry
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
//如果为Entry对象为null,则创建Entry
tab[i] = new Entry(key, value);
//Entry对象的个数加一
int sz = ++size;
//进行扩容
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
- 计算Hash值的方法
可以看到和我们的 HashMap 计算方式有所区别,他会根据集合的长度,每次调用得到一个小于长度,并且不会和以前相同的数,也就是说他没有链表的结构,由于 ThreadLocal 他并不是一个专门存储数据的结构,他仅仅只是为了解决数据共享的安全问题,所以他认为不需要再使用链表来增加复杂度。
- 简单了解一下扩容方法
- 创建一个新的 Entry集合将原来的长度扩大为2倍
- 重新计算hash值,将原集合中的数据放入对应的Entry中
- 更新 threadLocal 对象中的属性
- 简单了解 remove 方法
- 计算threadLocal对象对应的hash值
- 删除节点
- 需要注意的问题 ThreadLocal 他仅仅只能解决基本类型的安全问题,如果你在 withInitial() 方法中返回了一个已经存在的对象,那么每个线程操作的依然是唯一的那个对象,所以解决不了对象的安全问题。