一、ThreadLocal是什么
ThreadLocal属于java.lang包,是一个泛型类的线程本地变量,在每一个线程中会创建一个实例的副本,通常用private static修饰ThreadLocal变量。使用场景是:变量需要在线程间隔离但是在方法或类间共享。使用方法:声明成员变量
private static final ThreadLocal<String> threadLocal = new ThreadLocal<>();
// 方法中使用threadLocal.set(params);
二、源码解析
ThreadLocal类的内部维护了一个ThreadLocalMap的静态内部类,使用ThreadLocal的set和get方法时,实际使用的是ThreadLocalMap的set和get方法。具体看源码:
set方法:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
get方法:
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
return (T)map.get(this);
// Maps are constructed lazily. if the map for this thread
// doesn't exist, create it, with this ThreadLocal and its
// initial value as its only entry.
T value = initialValue();
createMap(t, value);
return value;
}
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) {
//清空key
e.clear();
//清空value
expungeStaleEntry(i);
return;
}
}
}
ThreadLocalMap中的Entry是ThreadLocalMap中的一个静态内部类,且继承了弱引用。下面解释一下强引用、软引用、弱引用和虚引用的区别。
-
强引用**(StrongReference)**:强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机抛出OutOfMemoryError错误,使程序异常终止。
-
软引用**(SoftReference)**:内存空间足够,垃圾回收器不会回收它;如果内存空间不足,就会回收这些对象的内存。软引用可用来实现内存敏感的高速缓存。软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。
-
弱引用**(WeakReference):**只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
-
虚引用**(PhantomReference)**: 虚引用主要用来跟踪对象被垃圾回收器回收的活动。虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之 关联的引用队列中。
static class Entry extends WeakReference<ThreadLocal> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal k, Object v) {
super(k);
value = v;
} }
三、使用ThreadLocal的常见问题
1.内存泄漏
由源码可知,Entry中的k为ThreadLocal的弱引用,下一次垃圾回收时会被清理掉,但是value是强引用,不会被清理掉,就会出现key为null但是value有值的情况。如果没有显式的调用remove方法,线程执行完后会,通过ThreadLocal对象持有的对象是不会被释放的。
2.脏数据
Thread类中的声明:
ThreadLocal.ThreadLocalMap threadLocals = null;
解决方法:尽量在try-finally块中调用remove方法回收。
四、ThreadLocal使用实例
运行结果:可知线程间共享一个变量,但是互不影响,这是因为ThreadLocal为每一个线程提供了单独的副本。