1. 概述
很多同学对ThreadLocal并不陌生, 但是可能大多数同学可能是知其然不知其所以然, 所以今天就来分析一下ThreadLocal中的奥妙.
个人知识面不是很广, 很多知识综合不起来, 本文只是针对ThreadLocal的源码进行解析.
2. 实战
先来看一个示例吧.
public class ThreadLocalDemo {
public static void main(String[] args) {
// 定义ThreadLocal
ThreadLocal<String> threadLocal = new ThreadLocal<String>(){
@Override
protected String initialValue() {
return "init value.";
}
};
// 启动线程A
new Thread(() -> {
// set的过程实际上是对当前Thread类实例对象中的属性值threadLocals进行赋值
// 值为 new ThreadLocalMap(this, firstValue);
// 其中的this是当前的threadLocal实例对象
threadLocal.set("我是线程A.");
// get的过程实际上是先获取当前的线程, 然后获取当前线程中的threadLocals属性值(ThreadLocalMap)
// 然后通过key来获取值, key就是当前的实例对象, 也就是threadLocal实例对象
System.out.println(Thread.currentThread().getName() + ": " + threadLocal.get());
// 移除
threadLocal.remove();
}, "线程A").start();
// 启动线程B
new Thread(() -> {
threadLocal.set("我是线程B.");
System.out.println(Thread.currentThread().getName() + ": " + threadLocal.get());
threadLocal.remove();
}, "线程B").start();
// 打印主线程中的值
System.out.println(Thread.currentThread().getName() + ": " + threadLocal.get());
threadLocal.remove();
}
}
看起来很奇怪, 只有一个threadLocal实例对象, 在不同的线程中设置了不同的值, 但是获取的时候却没有混乱. 看起来是不同线程之间的变量各自独立, 它是如何实现的呢? 继续往下看.
3. Thread与ThreadLocal的关系
打开Thread的源码发现, Thread类中有一个属性值是threadLocals, 是这样定义的:ThreadLocal.ThreadLocalMap threadLocals = null;
, 可以发现实际上引用的是ThreadLocal类中的内部类ThreadLocalMap.
那么ThreadLocalMap又是什么呢? 通过名字猜测是一个Map, 内部有一个Entry数组存储Entry实例, 而Entry继承了WeakReference(弱引用), 弱引用的特点就是在进行GC的时候, 弱引用会被回收掉. ThreadLocalMap中提供了Map的基本操作, 如set/get/remove.
这里可以猜测一下ThreadLocalMap中存储的key和value都是什么? 下面会有解答.
4. ThreadLocal#set
set无非就是复制操作, 方法名简单, 但是具体实现并不是简单的赋值操作.
public void set(T value) {
Thread t = Thread.currentThread(); // 获取当前线程.
ThreadLocalMap map = getMap(t); // 获取线程实例的threadLocals属性值.
if (map != null)
map.set(this, value); // this指的是threadLocal对象. value就是传入的值.
else
createMap(t, value); // 创建一个Map, 并设置value值.
}
// ----------被调用的方法--------------
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
可以这样理解: Thread类持有ThreadLocalMap类实例, 而ThreadLocalMap类以Map(数组)的形式持有n个Entry(Entry的key为ThreadLocal实例, value为存储数据).
5. ThreadLocal#get
同样, get方法也并不是简单的获取.
public T get() {
Thread t = Thread.currentThread(); // 获取当前线程
ThreadLocalMap map = getMap(t); // 获取threadLocalMap
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this); // 通过当前的threadLocal实例获取对应的值
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
// ----------被调用的方法--------------
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
这里比较简单, 就是先获取当前线程, 然后获取线程持有的threadLocalMap对象, 然后通过key(当前的threadLocal)获取对应value值.
但是这里这个setInitialValue方法是什么呢? 里面调用了initialValue方法, 这里返回了一个null值, 其实我们可以重写这个方法, 如下
// 定义ThreadLocal
ThreadLocal<String> threadLocal = new ThreadLocal<String>(){
@Override
protected String initialValue() {
return "init value.";
}
};
这样, 方法的返回值就是我们自定义的初始值了.
6. 关于内存泄漏
对于Java同学来说, 内存泄漏这个词比较陌生, 但是对于C的同学来说比较熟悉, 就是分配了内存之后没有进行回收, 因为C是手动分配内存手动进行回收, 而Java是自动的. 如果使用不当则会导致内存泄漏.
关于ThreadLocal的内存泄漏原因, 写文章的时候我还没有理解到位, 大家可以自行百度, 但是这里给出解决方法来避免可能的内存泄漏.
就是 每次使用完成之后要使用threalocal.remove();
从Thread的ThreadLocalMap中移除当前的ThreadLocal.
7. 总结
关于ThreadLocal要明白以下几点
- 了解他的运行原理
- 避免内存泄漏