ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

ThreadLocal 应用及原理、弱引用介绍、同线程内数据共享

2021-11-30 18:33:19  阅读:264  来源: 互联网

标签:Object value ThreadLocal 线程 数据共享 key null 引用


记录一下学习内容,若有错误感谢指正,互相交流,共同进步

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回收的是堆内存里的对象,不是引用对象

 若有错误感谢指正,互相交流,共同进步

 

标签:Object,value,ThreadLocal,线程,数据共享,key,null,引用
来源: https://blog.csdn.net/qq_41066277/article/details/121637581

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有