• 为什么会有ThreadLocal?

    提供了一个变量在线程间隔离而在类和方法间共享的一种方法。

虽然我们可以通过其他方法来实现相同的功能,但是使用ThreadLocal会更加的简洁和优雅。

ThreadLocal是如何实现的?

先看结构图,一步一步解释。
image.png

  • 大致流程

每个ThreadLocal实例 set 赋值的时候,会先获取到当前线程的ThreadLocals(基于弱引用自定义实现的一个Map),然后把弱引用key指向当前ThreadLocal实例。

  • ThreadLocals来源于哪里?

追踪发现,来源于当前线程:
image.png
image.png

  • 为什么使用弱引用

如上面的结构图,正常情况下我们new一个ThreadLocal的时候就有一个强引用指向当前变量,如果数组里的key是强引用,当我们set赋值后,又有一个引用指向当前变量,那么我们就无法通过下面的代码回收ThreadLocal:

  1. ThreadLocal<Object> current = new ThreadLocal<>();
  2. current.set(“str”)
  3. current = null;
  • 那么使用弱引用就可以避免内存泄露吗?

不一定,当我们把current设置为null后,下一次GC会把current内存回收,但是ThreadLocals里面存储的value并不能回收,还是会造成内存泄露,好在ThreadLocal的get和set,remove可以自动去把JVM已经GC掉的ThreadLocal对应的Entry给清理掉,这一点比强引用好,但是也并不保险。

  • 那么我们该如何使用ThreadLocal呢?

如下,我们可以及时释放不再使用的变量。
image.png

使用示例

Random随机数

Random在获取随机数的时候,会先通过CAS尝试修改seed,所以多线程下是不安全的。
image.png
解决方法:自己封装或者使用JDK8提供的ThreadLocalRandom。
image.png

SimpleDateFormat

SimpleDateFormat是线程不安全的,因为parse和format方法会频繁修改父类的calendar。
image.png
使用ThreadLocal封装的示例类:

  1. /**
  2. * 时间工具类
  3. */
  4. public class TimeUtil {
  5. public static final String YEAR_MONTH_DAY_SECOND = "yyyy-MM-dd HH:mm:ss";
  6. public static final String YEAR_MONTH_DAY_SECOND2 = "yyyy/MM/dd HH:mm:ss";
  7. public static final String YEAR_MONTH_DAY_SECOND3 = "yyyy年MM月dd日 HH时mm分ss秒";
  8. public static final String YEAR_MONTH_DAY = "yyyy-MM-dd";
  9. public static final String YEAR_MONTH_DAY2 = "yyyy年MM月dd日";
  10. /**
  11. * 采用 ThreadLocal 避免 SimpleDateFormat 非线程安全的问题
  12. * <p>
  13. * Key —— 时间格式
  14. * Value —— 解析特定时间格式的 SimpleDateFormat
  15. */
  16. private static ThreadLocal<Map<String, SimpleDateFormat>> sThreadLocal = new ThreadLocal<>();
  17. /**
  18. * 获取解析特定时间格式的 SimpleDateFormat
  19. *
  20. * @param pattern 时间格式
  21. */
  22. private static SimpleDateFormat getDateFormat(String pattern) {
  23. Map<String, SimpleDateFormat> strDateFormatMap = sThreadLocal.get();
  24. if (strDateFormatMap == null) {
  25. strDateFormatMap = new HashMap<>();
  26. }
  27. SimpleDateFormat simpleDateFormat = strDateFormatMap.get(pattern);
  28. if (simpleDateFormat == null) {
  29. simpleDateFormat = new SimpleDateFormat(pattern, Locale.getDefault());
  30. strDateFormatMap.put(pattern, simpleDateFormat);
  31. sThreadLocal.put(strDateFormatMap);
  32. }
  33. return simpleDateFormat;
  34. }
  35. /**
  36. * 时间格式化
  37. *
  38. * @param date:要格式化的时间
  39. * @param pattern:要格式化的类型
  40. */
  41. public static String formatDate(Date date, String pattern) {
  42. if (date == null || pattern == null) {
  43. return null;
  44. }
  45. return getDateFormat(pattern).format(date);
  46. }
  47. }

Spring事务

image.png