Skip to content

ThreadLocal

参考:

https://juejin.im/entry/5662895900b0bf3758a69736

https://blog.csdn.net/yanluandai1985/article/details/82590336

ThreadLocal概念

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

方法: get(), set(), remove()


示例:

java
package top.xinzhang0618.producer.context;

/**
 * BizContext
 *
 * @author xinzhang
 * @author Shenzhen Greatonce Co Ltd
 * @version 2019/12/23
 */
public final class BizContext {

  private static ThreadLocal<Long> localUserId = new ThreadLocal<>();
  private static ThreadLocal<String> localUserName = new ThreadLocal<>();

  public static void setUserId(Long userId) {
    localUserId.set(userId);
  }

  public static void setUserName(String userName) {
    localUserName.set(userName);
  }


  public static void removeUserId() {
    localUserId.remove();
  }

  public static void removeUserName() {
    localUserName.remove();
  }


  public static Long getUserId() {
    return localUserId.get();
  }

  public static String getUserName() {
    return localUserName.get();
  }
}

测试:

java
  @Test
  public void test() {
    BizContext.setUserName("测试1");
    System.out.println(Thread.currentThread().getName() + "----111------>" + BizContext.getUserName());
    ExecutorService executorService = Executors.newCachedThreadPool();
    executorService
        .submit(
            () -> System.out.println(Thread.currentThread().getName() + "----222------>" + BizContext.getUserName()));
    executorService.execute(TtlRunnable
        .get(() -> System.out.println(Thread.currentThread().getName() + "----333------>" + BizContext.getUserName())));
  }

结果:
main----111------>测试1
pool-2-thread-1----222------>null
pool-2-thread-1----333------>null

ThreadLocal内存泄漏问题

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

Entry源代码

java
static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;
 
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

解决方案:

添加web拦截器, 释放资源

图鸦示例

BizContextInterceptor

java
package com.greatonce.tuya.service;

import com.greatonce.tuya.util.BizContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

/**
 * BizContextInterceptor
 *
 * @author Shenzhen Greatonce Co Ltd
 * @author ginta
 * @version 2019/3/25
 */
@Component
public class BizContextInterceptor extends HandlerInterceptorAdapter {

  @Override
  public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
      throws Exception {
    super.afterCompletion(request, response, handler, ex);
    BizContext.removeNickname();
    BizContext.removeTenantId();
    BizContext.removeUserId();
    BizContext.removeSystemSetting();
  }
}

WebConfiguration

java
package com.greatonce.tuya.service;

import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson.serializer.ValueFilter;
import com.alibaba.fastjson.support.config.FastJsonConfig;
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import com.greatonce.core.Constants;
import com.greatonce.core.util.JsonUtil;
import com.greatonce.core.util.SecurityUtil;
import com.greatonce.tuya.service.converter.String2ListConverter;
import com.greatonce.tuya.service.converter.String2LocalDateConverter;
import com.greatonce.tuya.service.converter.String2LocalDateTimeConverter;
import java.io.IOException;
import java.io.InputStream;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.io.Resource;
import org.springframework.format.FormatterRegistry;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * @author ginta
 * @author Shenzhen Greatonce Co Ltd
 * @version 2018/6/2
 */
@Configuration
public class WebConfiguration implements WebMvcConfigurer {

  @Autowired
  BizContextInterceptor bizContextInterceptor;


  @Value("classpath:config/pkcs8_private_key.pem")
  private Resource privateKey;

  @Bean
  public PrivateKey loginPrivateKey()
    throws IOException, InvalidKeySpecException, NoSuchAlgorithmException {
    try (InputStream inputStream = privateKey.getInputStream()) {
      final List<String> lines = IOUtils.readLines(inputStream, Constants.CHARSET_UTF8);
      StringBuilder builder = new StringBuilder();
      for (String line : lines) {
        if (!line.startsWith("--")) {
          builder.append(line);
        }
      }
      String keyContent = builder.toString();
      return SecurityUtil.getPrivateKey(keyContent);
    }
  }

  @Override
  public void addFormatters(FormatterRegistry registry) {
    registry.removeConvertible(String.class, Collection.class);
    registry.addConverter(new String2ListConverter((ConversionService) registry));
    registry.addConverter(String.class, LocalDate.class, new String2LocalDateConverter());
    registry.addConverter(String.class, LocalDateTime.class, new String2LocalDateTimeConverter());
  }

  @Override
  public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(bizContextInterceptor);
  }

  @Override
  public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
    FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
    Long2StringJsonFilter filter = new Long2StringJsonFilter();
    fastConverter.setSupportedMediaTypes(Arrays
      .asList(MediaType.APPLICATION_JSON, MediaType.APPLICATION_JSON_UTF8,
        MediaType.APPLICATION_FORM_URLENCODED, MediaType.TEXT_PLAIN));
    FastJsonConfig fastJsonConfig = new FastJsonConfig();
    fastJsonConfig.setParserConfig(JsonUtil.PARSER_CONFIG);
    fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat);
    fastJsonConfig.setSerializeFilters(filter);
    fastConverter.setFastJsonConfig(fastJsonConfig);
    converters.clear();
    converters.add(fastConverter);
  }

  static class Long2StringJsonFilter implements ValueFilter {

    @Override
    public Object process(Object object, String name, Object value) {
      if (value instanceof Long) {
        return String.valueOf(value);
      }
      return value;
    }
  }
}

TransmittableThreadLocal

参考: https://www.jianshu.com/p/e0774f965aa3

官方: https://github.com/alibaba/transmittable-thread-local

需求场景: 在使用线程池等会池化复用线程的执行组件情况下传递ThreadLocal

使用步骤

1.导依赖

java
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>transmittable-thread-local</artifactId>
    <version>2.10.2</version>
</dependency>

2.BizContext

java
/**
 * BizContext
 *
 * @author xinzhang
 * @author Shenzhen Greatonce Co Ltd
 * @version 2019/12/25
 */
public class BizContext {
  private static ThreadLocal<Long> localUserId = new TransmittableThreadLocal<>();
  private static ThreadLocal<String> localUserName = new TransmittableThreadLocal<>();

  public static void setUserId(Long userId) {
    localUserId.set(userId);
  }

  public static void setUserName(String userName) {
    localUserName.set(userName);
  }


  public static void removeUserId() {
    localUserId.remove();
  }

  public static void removeUserName() {
    localUserName.remove();
  }


  public static Long getUserId() {
    return localUserId.get();
  }

  public static String getUserName() {
    return localUserName.get();
  }
}

3.Text

java
 @Test
  public void test() {
    BizContext.setUserName("测试1");
    System.out.println(Thread.currentThread().getName() + "----111------>" + BizContext.getUserName());
    ExecutorService executorService = Executors.newCachedThreadPool();
    executorService
        .submit(
            () -> System.out.println(Thread.currentThread().getName() + "----222------>" + BizContext.getUserName()));
    executorService.execute(TtlRunnable
        .get(() -> System.out.println(Thread.currentThread().getName() + "----333------>" + BizContext.getUserName())));
  }

结果:
main----111------>测试1
pool-2-thread-1----222------>测试1
pool-2-thread-1----333------>测试1