线程本地存储,提供线程内的局部变量,这些变量在线程的生命周期内起作用。减少同一个线程内多个函数或者组件之间一些公共变量的传递复杂度。简而言之就是:为每个线程分配一个局部变量,各个线程之间你的局部变量互不影响。

原理

ThreadLocal内部维护了一个ThreadLocalMap,就如同名字一样,这就是一个Map,key是当前的ThreadLocal实例,value是对应的值。
image.png
当前线程指向的是一个ThreadLocalMap,ThreadLocalMap中的key指向的是当前的ThreadLocal。

实际使用场景

我们在项目中经常会写一个DateTimeUtil的工具类,里面提供了获取各种时间格式的方法。但SimpleDateFormat是一个线程不安全的类,如果把SImpleDateFormat定义为全局变量的话,会出现并发问题,所以可以使用ThreadLocal来为每一个线程初始化一个SImpleDateFormat变量。

  1. public class DateTimeUtil {
  2. private static ThreadLocal<SimpleDateFormat> format1 = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
  3. private static ThreadLocal<SimpleDateFormat> format2 = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
  4. /**
  5. * 得到指定日期的一天的的最后时刻23:59:59
  6. */
  7. public static Date getFinallyDate(Date date) {
  8. String temp = format1.get().format(date);
  9. temp += " 23:59:59";
  10. try {
  11. return format2.get().parse(temp);
  12. } catch (ParseException e) {
  13. return null;
  14. }
  15. }
  16. /**
  17. * 获取当前日期 "yyyy-MM-dd HH:mm:ss"
  18. */
  19. public static Date getCurrentDateTime() {
  20. String temp = format2.get().format(new Date());
  21. try {
  22. return format2.get().parse(temp);
  23. } catch (ParseException e) {
  24. return null;
  25. }
  26. }
  27. }

内存泄露问题

ThreadLocal会出现内存泄露吗?
看ThreadLocal的源码,其内部维护了一个ThreadLocalMap,里面有个Entry的静态类,继承了WeakReference弱引用,所以当ThreadLocal实例被置为null,指向它的key因为是弱引用,所以ThreadLocal就会被GC,不会出现内存泄露问题。
但当ThreadLocal实例被GC之后,value就会永远不会被访问到,但有一条从currentThread过来的强引用,所以它不会被GC,直到线程结束,才会被GC。但是如果在ThreadLocal被GC之后到线程结束这段时间发生了内存泄露,这还是有问题的。比如使用线程池,线程结束之后是会被放到池子中的,有可能这个线程永远不会再被用到,所以value就永远不会被GC(除非关闭线程池)。
所以在调用ThreadLocal的get/set方法时,就会先清除一下ThreadLocalMap中key为null的值。但最坏的情况是:ThreadLocal被置为null了,所以ThreadLocal被GC啦,此时使用了线程池,线程周期结束被放回到池子中,没有被再次使用或者没有调用get/set方法,那就会出现内存泄露问题啦。