记录一下学习内容,若有错误感谢指正,互相交流,共同进步
ThreadLocal
摘要:
在堆内存中,有一个线程共享的哈希表ThreadLocalMap,可以通过该表实现线程内的参数传递,如用户信息、应用信息等基础信息,以便在方法调用过程中不需要重复传递,ThreaLocal更像是一个操作该表的工具对象
关系图
//执行应用代码
ThreadLocal<String> userId=new ThreadLocal<String>()
userId.set("1234")
//ThreadLocal 源码
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
// ThreadLocalMap 源码
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
当我们创建一个ThreadLocal对象 userId,并赋值1234时,通过源码发现,值内容“1234”并非保存在ThreadLocal对象内,而是通过ThreadLocal获取当前线程对象,并通过该对象获取当前线程所对应的ThreadLocalMap 对象,值内容“1234”保存在ThreadLocalMap 内的Entry对象的value里,而key则是ThreadLocal对象实例,此处注意Key为弱引用,后续会介绍
ThreadLocal 应用
场景之一,共享基础信息
1、创建工具类UserRuntimeEnv 定义基础信息对象
public class UserRuntimeEnv {
//这里的USERID和PHONE是 ThreadLocal对象的key
private static final ThreadLocal<String> USERID=new ThreadLocal<>();
private static final ThreadLocal<String> PHONE =new ThreadLocal<>();
public UserRuntimeEnv() {
}
public static String getPhone(){
return (String)PHONE.get();
}
public static void setPhone(String phone){
PHONE.set(phone);
}
public static void setUserId(String userId) {
USERID.set(userId);
}
public static String getUserId(){
return USERID.get();
}
public static void clear(){
PHONE.remove();
USERID.remove();
}
}
2、创建Aop类拦截请求,存入基础信息(可忽略自定义注解的内容)
public Object aroundApi(ProceedingJoinPoint point)throws Throwable{
String args=argsToString(point.getArgs());
log.info("日志统一打印 ↓ ↓ ↓ ↓ ↓ ↓ {}.{}() start ↓ ↓ ↓ ↓ ↓ ↓ ↓ ,参数:\n{}",
point.getSignature().getDeclaringTypeName(),//类名
point.getSignature().getName(),//方法名
args);//请求参数
StopWatch stopWatch=new StopWatch();//spring通用时间记录工具,ms级别
stopWatch.start();
RequestAttributes requestAttributes= RequestContextHolder.getRequestAttributes();
HttpServletRequest request=(HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);
String userId=request.getHeader("userId");
UserRuntimeEnv.setUserId(userId);//将用户信息存放到线程本地表,方便方法直接调用获取参数
MethodSignature methodSignature=(MethodSignature) point.getSignature();//获取方法对象
String declaringTypeName=methodSignature.getDeclaringTypeName();
String methodName=methodSignature.getName();
Object response=null;
//获取该方法的LogPrint注解信息,可能为null
LogPrint logPrint=methodSignature.getMethod().getAnnotation(LogPrint.class);
try {
response=point.proceed();
}finally {
stopWatch.stop();
if(null==logPrint||logPrint.isPrint()){
log.info("统一日志打印(end): {}.{}() ↑ ↑ ↑ ↑ ↑,响应时间:{}毫秒,响应内容:\n{}",
declaringTypeName, methodName, stopWatch.getTotalTimeMillis(), argsToString(response));
}
}
//这种情况下,线程已结束,可清除可不清除
UserRuntimeEnv.clear();
return response;
}
3、在整个线程的方法执行中均可通过
UserRuntimeEnv.getUserId 获取相应用户信息
弱引用
摘要
对象当可达性只有弱引用时,当系统触发gc时对象会被回收掉
//这种情况下,线程已结束,可清除可不清除
UserRuntimeEnv.clear();
//ThreadLocalMap源码
private void set(ThreadLocal<?> key, Object value) {
// We don't use a fast path as with get() because it is at
// least as common to use set() to create new entries as
// it is to replace existing ones, in which case, a fast
// path would fail more often than not.
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
在上述示例中可看到ThreadLocal对象可清除可不清除,不清除不会造成内存泄露吗?不会,因为ThreadLocalMap有个回收机制,从ThreadLocalMap源码可见当设置value时会检索entry对象并清除key为null的对象
也就是说当key为null时对象会自动回收,
那为什么key怎么才会null呢?
这就提到刚刚的弱引用定义,当对象只被弱引用指向时,会在gc中被自动回收。如果key对象只被弱引用指向,则会在gc后变成null
// ThreadLocalMap 源码
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
从ThreadLocalMap 源码可见,正如上图所示,虚线是弱引用,在线程执行中,key指向ThreadLocal是弱引用指向,threadLocalRef(userId的引用)指向ThreadLocal是强引用指向,当线程执行完毕,线程栈内的引用清空,ThreadLocal就只剩下弱引用指向,则会在gc后回收,相应value也会在后续回收
弱引用介绍
//创建两个强引用指向的对象
Object objA=new Object();
Object objB=new Object();
//创建两个弱引用weakA、weakB和一个强引用strongA;
WeakReference<Object> weakA=new WeakReference<>(objA);
WeakReference<Object>weakB=new WeakReference<>(objB);
Object strongA=objA;
此时取消objA和objB的引用
objA=null;
objB=null;
执行gc后
System.gc();
执行完后,Object@0002对象被回收,注意这里gc回收的是堆内存里的对象,不是引用对象
若有错误感谢指正,互相交流,共同进步