是什么

顾名思义线程本地化,对于相同的变量,可以让不同的线程存储的值不一样。也就是说:对于同一个变量,不同的线程看到的值是不一样的,之前咱们说过volatile是为了保证线程之间的可见性,这个ThreadLocal它可以让线程之间的数据不可见。官网解释:

This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its {@code get} or {@code set} method) has its own, independently initialized copy of the variable. {@code ThreadLocal} instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID) 该类提供了线程局部 (thread-local) 变量。这些变量不同于它们的普通对应物,因为访问某个变量(通过其 get 或 set 方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。ThreadLocal 实例通常是类中的 private static 字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。

为什么

为什么ThredLocal可以让不同线程之间的值不一样(对于同一个static变量)?我们来看ThredLocal源码的 set()和get()方法。set时先获取当前线程,它查看的结果是一个map,map的key值是当前线程的ThreadLocal,肯定其他线程get的时候,拿的key和别的线程不一样,所以获取不到哦。

  1. public void set(T value) {
  2. Thread t = Thread.currentThread();
  3. ThreadLocalMap map = getMap(t);
  4. if (map != null)
  5. map.set(this, value);
  6. else
  7. createMap(t, value);
  8. }
  1. public T get() {
  2. Thread t = Thread.currentThread();
  3. ThreadLocalMap map = getMap(t);
  4. if (map != null) {
  5. ThreadLocalMap.Entry e = map.getEntry(this);
  6. if (e != null) {
  7. @SuppressWarnings("unchecked")
  8. T result = (T)e.value;
  9. return result;
  10. }
  11. }
  12. return setInitialValue();
  13. }

应用场景

常应用于session管理、spring事务管理和数据库连接管理。比如spring事务管理应用中,在数据库连接中,肯定有多个dbConnection中,那同一个事务可能有1-3个方法需要调用获取connection,必需要保证这3个方法获取到的connection是一个,否则怎么保证事务啊。

问题

可能会造成内存泄漏。所以要养成一个好习惯,threadlocal用完了remove掉。
为什么会造成内存泄漏?为什么entry的key是弱引用指向threadlocal实例对象?如果线程迟迟不结束会出现什么情况?如图实线是强引用,虚线是弱引用。
image.png

  1. public static void test(){
  2. Thread thread = new Thread(new Runnable() {
  3. @Override
  4. public void run() {
  5. /**
  6. * 弱引用 场景应用
  7. * tl 是通过强引用指向了 threadlocal对象,
  8. * 而里边map的key是通过弱引用指向的threadlocal对象。
  9. */
  10. ThreadLocal<M> tl = new ThreadLocal<>();
  11. tl.set(new M());
  12. System.out.println(tl.get());
  13. tl.remove();
  14. System.out.println(tl.get());
  15. }
  16. });
  17. thread.start();
  18. }

第一种内存溢出:tl是强引用指向threadlocal对象,如果方法结束,那栈中tl也就不再指向堆中的 threadlocal对象,按说是可以回收堆中的 threadlocal对象。但如果entry中key指向的threadlocal不是弱引用的话,那岂不是threadlocal回收不了?所以entry的key是弱引用指向threadlocal对象。
第二种内存溢出:用完threadlocal一定要remove掉,否则会有内存溢出风险。场景:方法结束了,tl不再指向threadlocal对象,entry的key也是弱引用指向threadlocal对象,所以GC的时候threadlocal被回收了。这时entry的key是null,如果线程迟迟不结束,那entry的value就一直会被强引用。尤其是线程池场景下,方法结束,线程运行完不会退出线程,而是继续让线程池管理,那这就尴尬的内存泄漏了。

参考文章:写的很棒 https://www.cnblogs.com/xzwblog/p/7227509.html