1、概述
ThreadLocal类用来提供线程内部的局部变量。这些变量在多线程环境下访问(通过get或set方法访问)时能保证各个线程里的变量相对独立于其他线程内的变量,ThreadLocal实例通常来说都是private static类型。
总结:ThreadLocal不是为了解决多线程访问共享变量,而是为每个线程创建一个单独的变量副本,提供了保持对象的方法和避免参数传递的复杂性。
ThreadLocal的主要应用场景为按线程多实例(每个线程对应一个实例)的对象的访问,并且这个对象很多地方都要用到。例如:同一个网站登录用户,每个用户服务器会为其开一个线程,每个线程中创建一个ThreadLocal,里面存用户基本信息等,在很多页面跳转时,会显示用户信息或者得到用户的一些信息等频繁操作,这样多线程之间并没有联系而且当前线程也可以及时获取想要的数据。
每个thread中都存在一个map, map的类型是ThreadLocal.ThreadLocalMap. Map中的key为一个threadlocal实例. 这个Map的确使用了弱引用,不过弱引用只是针对key. 每个key都弱引用指向threadlocal. 当把threadlocal实例置为null以后,没有任何强引用指向threadlocal实例,所以threadlocal将会被gc回收. 但是,我们的value却不能回收,因为存在一条从current thread连接过来的强引用. 只有当前thread结束以后, current thread就不会存在栈中,强引用断开, Current Thread, Map, value将全部被GC回收.
所以得出一个结论就是只要这个线程对象被gc回收,就不会出现内存泄露,但在threadLocal设为null和线程结束这段时间不会被回收的,就发生了我们认为的内存泄露。其实这是一个对概念理解的不一致,也没什么好争论的。最要命的是线程对象不被回收的情况,这就发生了真正意义上的内存泄露。比如使用线程池的时候,线程结束是不会销毁的,会再次使用的。就可能出现内存泄露。
更多关于ThreadLocal文章介绍:
2、ThreadLocal各个类之间的层级关系
class Thread {// 当前线程对象
class ThreadLocalMap {// ThreadLocalMap对象,作为Thread的属性
Entry[] table;// 存放多种threadLocal对象对应的值
class Entry {//内部类
Object value;// 需要存放的值
ThreadLocal<T> key;// 以threadLocal作为key
}
}
}
3、ThreadLocal中get、set方法源码分析
ThreadLocal get方法源码分析(在第一次调用theadlocal的get、set方法是都会创建ThreadLocalMap对象)
public T get() {
Thread t = Thread.currentThread();//获取当前线程
ThreadLocalMap map = getMap(t);//根据当前线程对象获取threadLocalMap对象
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);//获取Entry对象
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;//获取对应ThreadLocal的变量值
return result;
}
}
return setInitialValue();
}
TheadLocalMap中getEntry方法源码
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);//找到当前threadLocal对应下标
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
setInitiaValue方法源码
private T setInitialValue() {
T value = initialValue();//初始化值默认为null
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);//实例化Entry对象,包含threadLocal对象以及变量,并将Entry对象写入table数组
else
createMap(t, value);//将当前线程Thread的TheadLocalMap对象实例化,新建一个Entry(threadLocal,value)对象并写入的table数组
return value;
}
ThreadLcoal set方法源码分析
public void set(T value) {
Thread t = Thread.currentThread();//获取当前线程对象
ThreadLocalMap map = getMap(t);//根据当前线程获取ThreadLocalMap对象
if (map != null)
map.set(this, value);//将value值存入Entry[] table中
else
createMap(t, value);//为当前线程新建ThreadLocalMap对象
}
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.
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)]) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);//实例化一个Entry对象
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
新建ThreadLocalMap对象源码
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);
}