ThreadLocal是干什么的?
用来隔离线程之间参数对象的,简单来说就是每个线程在使用的时候,都获取到每个线程上所绑定参数,举个例子,web开发项目当中,每个用户登录进来,访问网站,都会有个新线程创建,我们在每个线程中绑定用户信息,这样就可以在任何时刻获取到当前用户的信息,避免了参数传递。
先来看一个简单的例子
public class ThreadLocalTest1 {
ThreadLocal<Long> longLocal = ThreadLocal.withInitial(()->
Thread.currentThread().getId()
);
ThreadLocal<String> stringLocal = ThreadLocal.withInitial(() -> Thread.currentThread().getName());
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 Exception{
final ThreadLocalTest1 test = new ThreadLocalTest1();
// test.set(); 如果没有重写initValue方法会导致抛出异常
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
我们在每个线程当中都保存了各自的线程id和线程名称,
ThreadLocal 当中get和set方法的实现
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();
}
1、获取到当前线程
2、从当前线程中查询到线程内部的变量ThreadLocalMap
3、如果当前线程中维护的ThreadLocalMap不为null , 查询map中,key为ThreadLocal的value值
4、如果当前线程ThreadLocalMap还没初始化,执行setInitialValue
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;
}
1、获取到ThreadLocal中initialValue值
2、判断当前线程中是否包含ThreadLocalMap,
3、如果当前线程中含有ThreadLocalMap, 放入进去当前ThreadLocal, 和ThreadLocal中initialValue值,
4、如果没有ThreadLocalMap, 创建一个Map,
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
这段代码很简单就是创建一个新的ThreadLocalMap,将当前ThreadLocal及ThreadLocal 中initialValue的值传递进去
总结起来就是:
每个线程都含有自己的ThreadLocalMap<ThreadLocal,Object>, Obejct的值,就是我们需要为每个线程所保存的一个数据,如果没有重写ThreadLocal当中的initialValue方法,需要调用ThreadLocal的set方法将数据保存进去,否则会抛出空指针异常,如果重写了initialValue方法,在get时,会将initialValue保存进去
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
1、获取到当前线程中保存的ThreadLocalMap,
2、如果ThreadLocalMap不为空,将当前ThreadLocal和value放置进去
3、如果为null, 创建一个新的ThreadLocalMap
如果ThreadLocal当中value是一个共总对象,那么每个线程之间,关于这个对象的保存并不是隔离开的
如下例子:
public class ThreaLocalTest2 {
private static A a = new A();
private static final ThreadLocal<A> threadLocal = ThreadLocal.withInitial(()->a);
public static void main(String[] args) {
Thread[] threads = new Thread[5];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(() ->{
threadLocal.get().setNumber(threadLocal.get().getNumber() + 5);
System.out.println(Thread.currentThread().getName() + ":"
+ threadLocal.get().getNumber());
},"Thread-"+i);
}
for(Thread thread : threads) {
thread.start();
}
}
}
class A{
private int number = 0;
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
}
执行结果:
Thread-0:5
Thread-2:15
Thread-1:10
Thread-4:20
Thread-3:25
这个结果显然并不是我们想要的,原因就在于他们访问的是同一个对象,如果将ThreadLocal.withInitial(() ->new A()),就可以保证每个线程访问的数据之间是隔离开来的
ThreadLocal内存泄漏
先来看下ThreadLocal当中ThreadLocalMap的类
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
}
ThreadLocalMap当中entry维护了ThreadLocal的弱引用,其内存管理
关于强引用 A a = new A(); B b = new B();
C c = new C(b);
如果b = null , 那么在垃圾回收时,并不会将b回收走,原因是在c 内部维护了b的强引用,如果c = null ,那么b 是会被回收走的,还有一种情况就是 WeakReference weak = new WeakReference(b),c的内部使用weak, 这样在b = null 时,b就会被垃圾回收收走。
在ThreadLocalMap当中维护了ThreaLocal的弱引用,当threadLocal为null 时,就可以被回收走,但是ThreadLocalMap还维护了value, 而value是强引用,只有等当前线程消亡了之后,才会被垃圾回收掉,但是如果是在线程池中,线程一直被重复使用,value将永远不会被回收,就会发生内存泄漏,
但是,ThreadLocal中的ThreaLocalMap在setEntry和getEntry,会对key进行判断,如果ThreadLocal为null, 也会将value置为null
但是如果分配了内存之后,并没有调用ThreadLocal中的get, set 或者remove方法,那么key为null的,value值将不会被垃圾回收掉,建议在使用ThreadLocal时,即使进行清理,防止内存泄漏,尤其是在线程池当中使用ThreadLocal.