什么是ThreadLocal
1、简单使用
简单来说,ThreadLocal就是线程局部变量,是为了实现线程间的数据隔离.
下面来看一个小例子,在main线程中我们给t1设置了值,延时一秒使main线程在thread1线程前执行
public class ThreadLocalTest {
//新建一个ThreadLocal对象
static ThreadLocal<String> t1 = new ThreadLocal<>();
public static void main(String[] args) throws InterruptedException {
t1.set("threadLocal");//调用set方法设置值
System.out.println(Thread.currentThread().getName() + ":" + t1.get());
Thread.sleep(1000);
new Thread(()->{
System.out.println(Thread.currentThread().getName() + ":" + t1.get());
},"thread1").start();
t1.remove();
}
}
//执行结果
main:threadLocal
thread1:null
从上面的例子我们可以看出,t1在main线程中设置了值,但是只能在main线程中get到,在thread1中get不到值,实现了线程间数据的隔离
为什么会这样,ThreadLocal是如何保存数据的?
2、源码初探
//public class ThreadLocal<T>
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);//getMap
if (map != null)
map.set(this, value);//这里的this指的是ThreadLocal t1
else
createMap(t, value);
}
//getMap方法拿到当前线程的成员变量threadLocals
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
//public class Thread implements Runnable
ThreadLocal.ThreadLocalMap threadLocals = null;
通过ThreadLocal的set方法源码,我们可以看出,t1.set("threadLocal")
并不是往t1中设置了值,而是往当前线程的成员变量ThreadLocalMap
中put值,看完上面的源码后我们大胆猜测set后的结果如下图所示:
然而事实并非我们所想,源码中ThreadLocalMap
的Key值采用的是key
弱引用对象。
让我们看看set方法源码
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table; //熟悉的entry数组?
int len = tab.length;
int i = key.threadLocalHashCode & (len-1); //计算数组下标
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) { //找到K值,覆盖之前的值
e.value = value;
return;
}
if (k == null) {//先忽略~~
replaceStaleEntry(key, value, i);
return;
}
}
//没有找到K值,则new一个新的Entry对象,看看Entry的构造方法
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
//使用弱引用当做Entry的K值
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
这里我们可以看到,源码中使用ThreadLocal对象的弱引用。
个人理解:假设main线程中的t1不再使用,也就是说t1指向堆内存中ThreadLocal对象的引用没有后,main线程ThreadLocalMap的其中一个key会一直有引用指向此ThreadLocal对象,导致GC无法收集,造成 内存泄露。使用弱引用可以有效的防止内存泄漏问题,弱引用是只要发生GC就会被回收。
你以为到这里就结束了吗?并没有,当ThreadLocal对象被GC回收以后,我们的ThreadLocalMap会存在一个Key值为null的Entry对象,这个对象的value值是无法拿到的,这就产生了新的问题。。。
要解决这个问题,就要求我们不再使用ThreadLocal时,调用ThreadLocal.remove()
方法清楚Map中的数据。这时候心中又产生了一丝丝疑惑:既然都有了remove方法,那还需要弱引用干啥?
remove方法源码
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
private void remove(ThreadLocal<?> key) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
//算出下标,循环找到对应的Entry对象clear掉
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
e.clear();
expungeStaleEntry(i);
return;
}
}
}
看了remove的源码后,感觉弱引用就是为了防止我没有调用remove方法,一定程度的降低内存泄漏问题的存在,使用ThreadLocal千万记得remove。