首先我们应该了解的基础就是ThreadLocal是本地线程的专属对象存储,不同的线程不可以共享这个对象,下面是一个简单的例子:
public class threadLocalDemo1 {
static ThreadLocal<Person> threadLocal=new ThreadLocal<>();
public static void main(String[] args) {
new Thread(()->{
threadLocal.set(new Person("张三"));
System.out.println(threadLocal.get());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(()->{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(threadLocal.get());
}).start();
}
static class Person{
private String name;
public Person(String name){
this.name=name;
}
}
}
结果如下:
这个例子证明了不同的线程之间是无法共享我们的ThreadLocal的,还有一个要点就是ThreadLocal只能够存储一个变量,如果某个线程要存储多个对象那么就使用多个ThreadLocal即可,相比将对象加上static来说,ThreadLocal能够避免高并发下属性的不同。
来看一下threadLocal的set的源码:
public void set(T value) {
Thread t = Thread.currentThread();//获取当前正在运行的线程
ThreadLocalMap map = getMap(t);//获取线程中的ThreadLocalMap属性
if (map != null)
map.set(this, value);//将ThreadLocal作为键 我们传入的值作为值放入Map中
else
createMap(t, value);
}
getMap就是从当前线程中获取它的threadLocal属性
我们看一看ThreadLocalMap的set方法:
private void set(ThreadLocal<?> key, Object value) {
// We don't use a fast path as with get() because it is at
// least as common to use set() to create new entries as
// it is to replace existing ones, in which case, a fast
// path would fail more often than not.
//threadLocalMap的私有属性 用来存储数据我们的数据被他封装为了一个Entry对象
Entry[] tab = table;
int len = tab.length;
//计算value在tab中合适的插入位置 2进制计算恐怖如斯
int i = key.threadLocalHashCode & (len-1);
//从此处开始遍历
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
//观察当前的ThreadLocal是否已经存在了 存在就修改value的值
if (k == key) {
e.value = value;
return;
}
//如果为空说明 key已经被垃圾回收掉了 因为Entry是存在的 但是我们的threadLocal却消失了 说明被GC清理了 可能做的是 删除以前的 给我重新创建了一个Entry
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
//如果该位置没有Entry对象 证明第一次来存储 直接给他新建一个对象
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
我们看一看Entry对象的源码:
//这个牛b 直接继承了弱引用
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);//这句话 意思就是 用一个弱引用指向了ThreadLocal
value = v;//我们的value值就是直接强引用
}
}
用一张图来表示我们的关系:
为什么要使用弱引用,因为当我们的ThreadLocal对象不想使用了过后我们取消了指向ThreadLocal的强引用,但是它和Entry对象之间还存在引用关系所以无法清理掉垃圾,换成弱引用ThreadLocal就会被清理掉,防止内存泄漏。
但是我们的value对象并没有被回收,所以需要我们去调用remove方法来去除掉value的引用,让它能够被垃圾回收器回收掉,所以每次不需要使用我们的某个ThreadLocal所对应的对象过后需要调用remove去清除掉。
private void remove(ThreadLocal<?> key) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
//将弱引用清除
e.clear();
//整理数组 并且去除掉引用
expungeStaleEntry(i);
return;
}
}
}