本文共 5097 字,大约阅读时间需要 16 分钟。
ThreadLocal是干什么的?
用来隔离线程之间参数对象的,简单来说就是每个线程在使用的时候,都获取到每个线程上所绑定参数,举个例子,web开发项目当中,每个用户登录进来,访问网站,都会有个新线程创建,我们在每个线程中绑定用户信息,这样就可以在任何时刻获取到当前用户的信息,避免了参数传递。
先来看一个简单的例子
public class ThreadLocalTest1 { ThreadLocallongLocal = ThreadLocal.withInitial(()-> Thread.currentThread().getId() ); ThreadLocal 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()); }}
执行结果:
1main11Thread-01main
我们在每个线程当中都保存了各自的线程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 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:5Thread-2:15Thread-1:10Thread-4:20Thread-3:25
这个结果显然并不是我们想要的,原因就在于他们访问的是同一个对象,如果将ThreadLocal.withInitial(() ->new A()),就可以保证每个线程访问的数据之间是隔离开来的
先来看下ThreadLocal当中ThreadLocalMap的类
static class ThreadLocalMap { static class Entry extends WeakReference> { /** 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.
参考: