参考:
https://juejin.im/entry/5662895900b0bf3758a69736
https://blog.csdn.net/yanluandai1985/article/details/82590336
ThreadLocal概念
ThreadLocal的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度。
方法: get(), set(), remove()
**
示例:
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();
}
}
测试:
@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源代码
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
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
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.导依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
<version>2.10.2</version>
</dependency>
2.BizContext
/**
* 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
@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