1. ThreadLocal介绍
ThreadLocal顾名思义,就是线程的本地变量,只有当前线程可见,对其他线程来说是封闭且隔离的。每一个线程为自己本身创建ThreadLocal变量,只有当前线程可以访问,其他的线程不可以,从根源上避免了多个线程对共享资源的竞争问题,提高程序的执行效率。
2. ThreadLocal的基本使用
这里以创建两个线程的ThreadLocal为例子,来说明ThreadLocal的基本使用,相关代码如下:
private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
public static void print(String threadName) {
System.out.println("线程名:" + threadName + " 线程变量:" + threadLocal.get());
// 移除线程变量
threadLocal.remove();
}
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
threadLocal.set("local1");
print("线程1");
System.out.println("after remove:" + threadLocal.get());
}
}, "线程1").start();
new Thread(new Runnable() {
@Override
public void run() {
threadLocal.set("local2");
print("线程2");
System.out.println("after remove:" + threadLocal.get());
}
}, "线程2").start();
}
程序结果如下:
可以看到每一个线程都获取到了本身的线程变量,线程之间相互不影响。
3. ThreadLocal的实现原理
ThreadLocal如何实现线程变量之间互相不影响的呢?很简单,每一个线程都保存一份变量副本即可,下面将从设置值到取值的整个过程来说明。
ThreadLocal的设置值的方法是set, 源码如下:
public void set(T value) {
// 获取当前线程对象
Thread t = Thread.currentThread();
// 获取ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
if (map != null)
// 不为空,直接将新值覆盖旧值
map.set(this, value);
else
// 为空则进行初始化
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
ThreadLocal.ThreadLocalMap threadLocals = null;
getMap方法返回的是每一个线程的threadLocals属性,threadLocals属性为ThreadLocal.ThreadLocalMap类型,保存每一个线程的ThreadLocal变量,其初始化在map=null的情况下进行,执行 createMap(t, value)方法进行初始化。
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
// 通过hash运算计算出threadLocal的位置
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
ThreadMap中的Entry的key为弱引用类型(这也就是为什么TheadLocal会存在内存泄漏的原因,后面解释)。当我们要进行取值时,则执行如下get方法,将ThreadLocal中的Entry取出,如果不存在,则会进行清理操作。
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
// 不为空,则直接将之取出
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
// 为空,执行初始化操作
return setInitialValue();
}
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
// 执行清理操作
return getEntryAfterMiss(key, i, e);
}
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
while (e != null) {
ThreadLocal<?> k = e.get();
if (k == key)
return e;
if (k == null)
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
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;
}
4. ThreadLocal的内存泄漏问题的理解
ThreadLocal变量间的引用关系如下图所示:
当我们的ThreadLocal引用变成null时,由于ThreadMap的生命周期和当前线程一样,当前线程不结束,系统不会进行垃圾回收,这就造成一个现象:Entry中的key=null, 而value却存在,但我们无法在获取到value的值,这就造成内存泄漏。虽然在get方法中,当我们获取不到key的值时,会执行getEntryAfterMiss进行垃圾清理,但如果我们就一直访问key存在的Entry,getEntryAfterMiss方法就无法执行,内存泄漏还是存在,最稳妥的方法就是我们每次用完后都执行Remove操作,将变量手动清理。