ThreadLocal,很多地方叫做线程本地变量,也有些地方叫做线程本地存储,其实意思差不多。可能很多朋友都知道ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。
源码
先了解一下ThreadLocal类提供的几个方法:
public T get() { }
public void set(T value) { }
public void remove() { }
protected T initialValue() { }
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();
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
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;
}
Thread类
ThreadLocal.ThreadLocalMap threadLocals = null;
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
使用
package com.bijian.study;
public class Test {
ThreadLocal<Long> longLocal = new ThreadLocal<Long>();
ThreadLocal<String> stringLocal = new ThreadLocal<String>();
public void set() {
longLocal.set(Thread.currentThread().getId());
stringLocal.set(Thread.currentThread().getName());
}
public long getLong() {
return longLocal.get();
}
public String getString() {
return stringLocal.get();
}
public static void main(String[] args) throws InterruptedException {
final Test test = new Test();
test.set();
System.out.println(test.getLong());
System.out.println(test.getString());
Thread thread1 = new Thread() {
public void run() {
test.set();
System.out.println(test.getLong());
System.out.println(test.getString());
};
};
thread1.start();
thread1.join();
System.out.println(test.getLong());
System.out.println(test.getString());
}
}
运行结果:
1
main
11
Thread-0
1
main
总结:
1)实际的通过ThreadLocal创建的副本是存储在每个线程自己的threadLocals中的;
2)为何threadLocals的类型ThreadLocalMap的键值为ThreadLocal对象,因为每个线程中可有多个threadLocal变量,就像上面代码中的longLocal和stringLocal;
注意问题
1.在进行get之前,必须先set,否则会报空指针异常;
如果想在get之前不需要调用set就能正常访问的话,必须重写initialValue()方法。
ThreadLocal<Long> longLocal = new ThreadLocal<Long>() {
protected Long initialValue() {
return Thread.currentThread().getId();
};
};
ThreadLocal<String> stringLocal = new ThreadLocal<String>() {
protected String initialValue() {
return Thread.currentThread().getName();
};
};
2.ThreadLocal变量一般要声名成static类型,即当前线程中只有一个T类型变量的实例,线程内可共享该实例数据且不会出问题,如将其声名成非static,则一个线程内就存储多个T类型变量的实例,有点存储空间的浪费,一般很少有这样的应用场景。另外根据实际情况,ThreadLocal变量声名时也多加上private final关键词表明它时类内私有、引用不可修改。
比如spring的RequestContextHolder
private static final ThreadLocal requestAttributesHolder =
new NamedThreadLocal(“Request attributes”);
3.在线程池环境下,由于线程是一直运行且复用的,使用ThreadLocal时会出现这个任务看到上个任务ThreadLocal变量值以及内存泄露等问题,解决方法就是在当前任务执行完后将ThreadLocal变量remove或设置为初始值