参考:
https://juejin.im/entry/5662895900b0bf3758a69736
https://blog.csdn.net/yanluandai1985/article/details/82590336

ThreadLocal概念

ThreadLocal的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度。
方法: get(), set(), remove()
**
示例:

  1. package top.xinzhang0618.producer.context;
  2. /**
  3. * BizContext
  4. *
  5. * @author xinzhang
  6. * @author Shenzhen Greatonce Co Ltd
  7. * @version 2019/12/23
  8. */
  9. public final class BizContext {
  10. private static ThreadLocal<Long> localUserId = new ThreadLocal<>();
  11. private static ThreadLocal<String> localUserName = new ThreadLocal<>();
  12. public static void setUserId(Long userId) {
  13. localUserId.set(userId);
  14. }
  15. public static void setUserName(String userName) {
  16. localUserName.set(userName);
  17. }
  18. public static void removeUserId() {
  19. localUserId.remove();
  20. }
  21. public static void removeUserName() {
  22. localUserName.remove();
  23. }
  24. public static Long getUserId() {
  25. return localUserId.get();
  26. }
  27. public static String getUserName() {
  28. return localUserName.get();
  29. }
  30. }

测试:

  1. @Test
  2. public void test() {
  3. BizContext.setUserName("测试1");
  4. System.out.println(Thread.currentThread().getName() + "----111------>" + BizContext.getUserName());
  5. ExecutorService executorService = Executors.newCachedThreadPool();
  6. executorService
  7. .submit(
  8. () -> System.out.println(Thread.currentThread().getName() + "----222------>" + BizContext.getUserName()));
  9. executorService.execute(TtlRunnable
  10. .get(() -> System.out.println(Thread.currentThread().getName() + "----333------>" + BizContext.getUserName())));
  11. }
  12. 结果:
  13. main----111------>测试1
  14. pool-2-thread-1----222------>null
  15. pool-2-thread-1----333------>null

ThreadLocal内存泄漏问题

下图虚线表示弱引用。ThreadLocal对象被GC回收了,那么key变成了null。Map又是通过key拿到的value的对象。所以,GC在回收了key所占内存后,没法访问到value的值,因为需要通过key才能访问到value对象。另外,如图所示的引用链:CurrentThread — Map — Entry — value ,所以,在当前线程没有被回收的情况下,value所占内存也不会被回收。所以可能会造成了内存溢出。
ThreadLocal - 图1

Entry源代码

  1. static class Entry extends WeakReference<ThreadLocal<?>> {
  2. /** The value associated with this ThreadLocal. */
  3. Object value;
  4. Entry(ThreadLocal<?> k, Object v) {
  5. super(k);
  6. value = v;
  7. }
  8. }

解决方案:
添加web拦截器, 释放资源
图鸦示例
BizContextInterceptor

  1. package com.greatonce.tuya.service;
  2. import com.greatonce.tuya.util.BizContext;
  3. import javax.servlet.http.HttpServletRequest;
  4. import javax.servlet.http.HttpServletResponse;
  5. import org.springframework.stereotype.Component;
  6. import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
  7. /**
  8. * BizContextInterceptor
  9. *
  10. * @author Shenzhen Greatonce Co Ltd
  11. * @author ginta
  12. * @version 2019/3/25
  13. */
  14. @Component
  15. public class BizContextInterceptor extends HandlerInterceptorAdapter {
  16. @Override
  17. public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
  18. throws Exception {
  19. super.afterCompletion(request, response, handler, ex);
  20. BizContext.removeNickname();
  21. BizContext.removeTenantId();
  22. BizContext.removeUserId();
  23. BizContext.removeSystemSetting();
  24. }
  25. }

WebConfiguration

  1. package com.greatonce.tuya.service;
  2. import com.alibaba.fastjson.serializer.SerializerFeature;
  3. import com.alibaba.fastjson.serializer.ValueFilter;
  4. import com.alibaba.fastjson.support.config.FastJsonConfig;
  5. import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
  6. import com.greatonce.core.Constants;
  7. import com.greatonce.core.util.JsonUtil;
  8. import com.greatonce.core.util.SecurityUtil;
  9. import com.greatonce.tuya.service.converter.String2ListConverter;
  10. import com.greatonce.tuya.service.converter.String2LocalDateConverter;
  11. import com.greatonce.tuya.service.converter.String2LocalDateTimeConverter;
  12. import java.io.IOException;
  13. import java.io.InputStream;
  14. import java.security.NoSuchAlgorithmException;
  15. import java.security.PrivateKey;
  16. import java.security.spec.InvalidKeySpecException;
  17. import java.time.LocalDate;
  18. import java.time.LocalDateTime;
  19. import java.util.Arrays;
  20. import java.util.Collection;
  21. import java.util.List;
  22. import org.apache.commons.io.IOUtils;
  23. import org.springframework.beans.factory.annotation.Autowired;
  24. import org.springframework.beans.factory.annotation.Value;
  25. import org.springframework.boot.web.servlet.ServletComponentScan;
  26. import org.springframework.context.annotation.Bean;
  27. import org.springframework.context.annotation.Configuration;
  28. import org.springframework.core.convert.ConversionService;
  29. import org.springframework.core.io.Resource;
  30. import org.springframework.format.FormatterRegistry;
  31. import org.springframework.http.MediaType;
  32. import org.springframework.http.converter.HttpMessageConverter;
  33. import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
  34. import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
  35. /**
  36. * @author ginta
  37. * @author Shenzhen Greatonce Co Ltd
  38. * @version 2018/6/2
  39. */
  40. @Configuration
  41. public class WebConfiguration implements WebMvcConfigurer {
  42. @Autowired
  43. BizContextInterceptor bizContextInterceptor;
  44. @Value("classpath:config/pkcs8_private_key.pem")
  45. private Resource privateKey;
  46. @Bean
  47. public PrivateKey loginPrivateKey()
  48. throws IOException, InvalidKeySpecException, NoSuchAlgorithmException {
  49. try (InputStream inputStream = privateKey.getInputStream()) {
  50. final List<String> lines = IOUtils.readLines(inputStream, Constants.CHARSET_UTF8);
  51. StringBuilder builder = new StringBuilder();
  52. for (String line : lines) {
  53. if (!line.startsWith("--")) {
  54. builder.append(line);
  55. }
  56. }
  57. String keyContent = builder.toString();
  58. return SecurityUtil.getPrivateKey(keyContent);
  59. }
  60. }
  61. @Override
  62. public void addFormatters(FormatterRegistry registry) {
  63. registry.removeConvertible(String.class, Collection.class);
  64. registry.addConverter(new String2ListConverter((ConversionService) registry));
  65. registry.addConverter(String.class, LocalDate.class, new String2LocalDateConverter());
  66. registry.addConverter(String.class, LocalDateTime.class, new String2LocalDateTimeConverter());
  67. }
  68. @Override
  69. public void addInterceptors(InterceptorRegistry registry) {
  70. registry.addInterceptor(bizContextInterceptor);
  71. }
  72. @Override
  73. public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
  74. FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
  75. Long2StringJsonFilter filter = new Long2StringJsonFilter();
  76. fastConverter.setSupportedMediaTypes(Arrays
  77. .asList(MediaType.APPLICATION_JSON, MediaType.APPLICATION_JSON_UTF8,
  78. MediaType.APPLICATION_FORM_URLENCODED, MediaType.TEXT_PLAIN));
  79. FastJsonConfig fastJsonConfig = new FastJsonConfig();
  80. fastJsonConfig.setParserConfig(JsonUtil.PARSER_CONFIG);
  81. fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat);
  82. fastJsonConfig.setSerializeFilters(filter);
  83. fastConverter.setFastJsonConfig(fastJsonConfig);
  84. converters.clear();
  85. converters.add(fastConverter);
  86. }
  87. static class Long2StringJsonFilter implements ValueFilter {
  88. @Override
  89. public Object process(Object object, String name, Object value) {
  90. if (value instanceof Long) {
  91. return String.valueOf(value);
  92. }
  93. return value;
  94. }
  95. }
  96. }

TransmittableThreadLocal

参考: https://www.jianshu.com/p/e0774f965aa3
官方: https://github.com/alibaba/transmittable-thread-local

需求场景: 在使用线程池等会池化复用线程的执行组件情况下传递ThreadLocal
使用步骤
1.导依赖

  1. <dependency>
  2. <groupId>com.alibaba</groupId>
  3. <artifactId>transmittable-thread-local</artifactId>
  4. <version>2.10.2</version>
  5. </dependency>

2.BizContext

  1. /**
  2. * BizContext
  3. *
  4. * @author xinzhang
  5. * @author Shenzhen Greatonce Co Ltd
  6. * @version 2019/12/25
  7. */
  8. public class BizContext {
  9. private static ThreadLocal<Long> localUserId = new TransmittableThreadLocal<>();
  10. private static ThreadLocal<String> localUserName = new TransmittableThreadLocal<>();
  11. public static void setUserId(Long userId) {
  12. localUserId.set(userId);
  13. }
  14. public static void setUserName(String userName) {
  15. localUserName.set(userName);
  16. }
  17. public static void removeUserId() {
  18. localUserId.remove();
  19. }
  20. public static void removeUserName() {
  21. localUserName.remove();
  22. }
  23. public static Long getUserId() {
  24. return localUserId.get();
  25. }
  26. public static String getUserName() {
  27. return localUserName.get();
  28. }
  29. }

3.Text

  1. @Test
  2. public void test() {
  3. BizContext.setUserName("测试1");
  4. System.out.println(Thread.currentThread().getName() + "----111------>" + BizContext.getUserName());
  5. ExecutorService executorService = Executors.newCachedThreadPool();
  6. executorService
  7. .submit(
  8. () -> System.out.println(Thread.currentThread().getName() + "----222------>" + BizContext.getUserName()));
  9. executorService.execute(TtlRunnable
  10. .get(() -> System.out.println(Thread.currentThread().getName() + "----333------>" + BizContext.getUserName())));
  11. }
  12. 结果:
  13. main----111------>测试1
  14. pool-2-thread-1----222------>测试1
  15. pool-2-thread-1----333------>测试1