ThreadLocal
使用
使用很简单,例子如下:
public class TestThreadLocal {
static ThreadLocal<Integer> THREADLOCAL = new ThreadLocal<>();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
THREADLOCAL.set(0);
// 打印为0
System.out.println("0号线程:" + THREADLOCAL.get());
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
// 打印为空
System.out.println("1号线程:" + THREADLOCAL.get());
}
});
t1.start();
t1.join(); // 让主线程不要那么快的运行后边的
t2.start();
}
}
ThreadLocal提供了线程内存储变量的能力,且每一个线程读取的变量是对应的互相独立的。(线程隔离)
原理
从set方法入手,我们可以看到它的数据都是保存在一个ThreadLocalMap 当中的。
/**
* Sets the current thread's copy of this thread-local variable
* to the specified value. Most subclasses will have no need to
* override this method, relying solely on the {@link #initialValue}
* method to set the values of thread-locals.
*
* @param value the value to be stored in the current thread's copy of
* this thread-local.
*/
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocalMap 提供了一个Entry的内部静态类,并继承了WeakReference,实现key值的弱引用。
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
具体的set过程,数据最终被封装成一个Entry对象,并保存在一个Entry类型的对象数组当中,索引值为当前线程的哈希码和Entry表(长度-1)的与(&)结果。当然了,如果该位置已经被其他线程占据,会通过nextIndex方法寻找下一个位置。
private void set(ThreadLocal<?> key, Object value) {
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);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
场景
参数传递
- 之前项目里头遇到过参数值需要一个一个从HttpServletRequest拿出的,而且多个类要用到(Controller层和Service层)。此时可以通过过滤器或者拦截器的方式,在过滤器或者拦截器里头统一处理,然后存放到ThreadLocal 中,后边的处理也可以直接从ThreadLocal 拿,因为是同一个线程。
注意
- 使用之后,记得清理,因为在一些线程池场景中,由于线程复用的关系会存在数据错乱的场景。
- 另外手动清理,也可以避免内存泄露的场景,从源码可以知道,在Entry中,key是弱引用,那么被弱引用关联的对象只能生存到下一次垃圾收集发生为止,可以被清楚。但value是强引用,这部分GC是不会清理的,积压过多,则有内存泄漏的风险。