ThreadLocal的作用
当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本
ThreadLocal常用方法
- public void set(Object value): 设置当前线程的线程局部变量的值。
- public Object get(): 该方法返回当前线程所对应的线程局部变量。
- public void remove(): 将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。
- protected Object initialValue(): 返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次。ThreadLocal中的缺省实现直接返回一个null。
TheadLocal的举例
public class ThreadId {
// Atomic integer containing the next thread ID to be assigned
private static final AtomicInteger nextId = new AtomicInteger(0);
// Thread local variable containing each thread's ID
private static final ThreadLocal<Integer> threadId =new ThreadLocal<Integer>() {
@Override protected Integer initialValue() {
return nextId.getAndIncrement();
}
};
// Returns the current thread's unique ID, assigning it if necessary
public static int get() {
return threadId.get();
}
}
ThreadLocal的源码分析
ThreadLocal实现的核心是ThreadLocalMap这个类。ThreadLocalMap是ThreadLocal类的一个静态内部类,它实现了键值对的设置和获取(对比Map对象来理解),每个线程中都有一个独立的ThreadLocalMap副本,它所存储的值,只能被当前线程读取和修改。ThreadLocal类通过操作每一个线程特有的ThreadLocalMap副本,从而实现了变量访问在不同线程中的隔离。因为每个线程的变量都是自己特有的,完全不会有并发错误。还有一点就是,ThreadLocalMap存储的键值对中的键是this对象指向的ThreadLocal对象(其实不是这样的, 解释见"TheadLocal与WeakReference"), 而值就是你所设置的对象了。
ThreadLocal与WeakReference
思考这样一个问题, ThreadLoca对象能不能被GC回收? 如果能, 是如何做到的?
答案当然是能被GC回收, 前提是ThreadLocal对象不被其他变量引用(即不在任何一下GC Roots开始的引用树上).
思考第二问题, ThreadLocal对象不是作为线程所持有的ThreadLocalMap存储的键值吗? 只要有线程存在, 不应该一直有变量指向ThreadLocal对象吗?
这是一个很让人费解的问题, 网上很多文章都是这么, 包括我前面也是这么介绍的, 其实不然,查看源码发现, ThreadLocalMap中存放的是Entry类型的数组, 数组的下标是根据ThrealLocal的hashcode值通过位运算得到的(threadLocalHashCode & (len - 1), 其中len是Entry数组的长度), 而Entry其实是WeakReference类型. 所以在ThreadLocalMap中并没有ThreadLocal对象的强引用, 一旦外界没有其他变量强引用该ThreadLocal对象, 只剩下了它的弱引用, 根据若引用的特性, 在下次垃圾回收时, 即个回收该ThreadLocal对象.
/**
* The table, resized as necessary.
* table.length MUST always be a power of two.
*/
private Entry[] table;
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
接着, 第三问题就来了. ThreadLocal对象被回收了, 而ThreadLocalMap中的Entry数组中还存放着该对象对应的value值呢? 怎么回收?
在ThreadLocal的set和remove方法执行过程中就会触发清除被回收对象对应的value操作。 比如set方法调用时有可能触发ThreadLocalMap的resize()方法, 而该方法会遍历整个Entry数组, 一旦某个元素的key为null(及弱引用被回收), 会把该元素中的value强制设置为null.
此外,在ThreadLocalMap中的set方法和cleanSomeSlots方法,以及remove方法中也有相关额清除操作。 下面是ThreadLocalMap中resize方法的源码:
private void resize() {
Entry[] oldTab = table;
int oldLen = oldTab.length;
int newLen = oldLen * 2;
Entry[] newTab = new Entry[newLen];
int count = 0;
for (int j = 0; j < oldLen; ++j) {
Entry e = oldTab[j];
if (e != null) {
ThreadLocal<?> k = e.get();
if (k == null) {
e.value = null; // Help the GC
} else {
int h = k.threadLocalHashCode & (newLen - 1);
while (newTab[h] != null)
h = nextIndex(h, newLen);
newTab[h] = e;
count++;
}
}
}
ThreadLocal与内存泄露
ThreadLocal对象被回收, 但该对象对应的value不能自动回收。 出现这种情况的条件: ThreadLocal是本地变量, 使用完后, 不在调用其set和remove方法
ThreadLocal不会被回收,value自然不会被自动回收, 比如定义成static类型, 这种情况value可能会被线程池中的同一个线程重复利用存放一下过期的的脏数据。
在使用线程池的情况下,没有及时清理ThreadLocal,不仅是内存泄漏的问题,更严重的是可能导致业务逻辑出现问题。所以,使用ThreadLocal就跟加锁完要解锁一样,用完就调用remove方法清理数据。