• ThreadLocal总结


    并发编程
    1、同一线程,不同组件中传递数据。
    2、线程隔离,每一个线程都是独立的,互不影响。

    结构及用法
    1、ThreadLocal 的内部 ThreadLocalMap,键为 ThreadLocal。
    2、ThreadLocal 的数据结构是个环形数组
    3、get,set 两个方法都不能完全防止内存泄漏,还是每次用完 ThreadLocal 都勤奋的 remove一下靠谱。
    4、ThreadLocalMap 采用开放地址法,ThreadLocal 往往存放的数据量不会特别大(而且key 是弱引用又会被垃圾回收,及时让数据量更小),这个时候开放地址法简单的结构会显得更省空间,同时数组的查询效率也是非常高,加上第一点的保障,冲突概率也低。

    ThreadLocalMap 的 Entry 的 key 是弱引用,如果外部没有强引用指向 key,key 就会被回收,而 value 由于 Entry 强引用指向了它,导致无法被回收,但是 value 又无法被访问,因此发生内存泄漏。
    关于内存泄漏,我们重点从源码层面分析了 get、set、remove 方法,并图文并茂的演示了 get、set 方法不能防止内存泄漏,而 remove 方法能防止内存泄漏的结论。

    问题1
    1、ThreadLocal中有有使用弱引用,为什么要用弱引用?用弱引用,发生一次gc后,set进去的值再get就是null了吗?

    图可以看出,ThreadLocal作为key,是有两条引用链的,一条是当前线程中的,由线程指向ThreadLocalMap,通过Map指向Entry,而Entry指向key;另一条引用链则是当前执行的主线程类的成员变量,且为强引用,所以目前来说并不会受到gc影响。

    问题2
    2、内存泄露直至OOM?

    public class TestThreadLocalLeak {
        final static ThreadLocal<byte[]> LOCAL = new ThreadLocal();
        final static int _1M = 1024 * 1024;
    
        public static void main(String[] args) {
            //testUseThread();
            testUseThreadPool();
        }
    
        /**
         * 使用线程
         */
        private static void testUseThread() {
            for (int i = 0; i < 100; i++) {
                new Thread(() ->
                        LOCAL.set(new byte[_1M])
                ).start();
            }
        }
    
        /**
         * 使用线程池
         */
        private static void testUseThreadPool() {
            ExecutorService executorService = Executors.newFixedThreadPool(100);
            for (int i = 0; i < 100; i++) {
                executorService.execute(() ->
                        LOCAL.set(new byte[_1M])
                );
            }
            executorService.shutdown();
        }
    }
    

    当调用testUseThread()时,系统在运行时执行了大量YGC,但始终稳定回收,最后正常执行,但是执行testUseThreadPool()时,经历的频繁的Full GC,内存却没有降下去,最终发生了OOM。
    我们分析一下,在使用new Thread()的时候,当线程执行完毕时,随着线程的终止,那个这个Thread对象的生命周期也就结束了,此时该线程下的成员变量,ThreadLocalMap是GC Root不可达的,同理,下面的Entry、里面的key、value都会在下一次gc时被回收;而使用线程池后,由于线程执行完一个任务后,不会被回收,而是被放回线程池以便执行后续任务,自然其成员变量ThreadLocalMap不会被回收,最终引起内存泄露直至OOM。至于怎么避免出现内存泄露,就是在使用线程完成任务后,如果保存在ThreadLocalMap中的数据不必留给之后的任务重复使用,就要及时调用ThreadLocal的remove(),这个方法会把ThreadLocalMap中的相关key和value分别置为null,就能在下次GC时回收了。

    参见:https://mp.weixin.qq.com/s/hMCVtX0bQXy38uBJfXgR2g

    传递用户信息

    package com.example.study.interceptor;
    
    public class LoginInterceptor implements HandlerInterceptor {
    
        private static final Logger logger = LoggerFactory.getLogger(LoginInterceptor.class);
    
    
        @Override
        public boolean preHandle(HttpServletRequest request,
                                 HttpServletResponse response, Object handler) throws Exception {
    
            Cookie[] cookieList = request.getCookies();
            if (cookieList == null) {
                return false;
            }
            String retValue = null;
            try {
                for (int i = 0; i < cookieList.length; i++) {
                    if (cookieList[i].getName().equals("userToken")) {
                        retValue = URLDecoder.decode(cookieList[i].getValue(), "UTF-8");
                        break;
                    }
                }
            } catch (UnsupportedEncodingException e) {
                logger.error("Cookie Decode Error.", e);
            }
    
            if (retValue  == null) {
                // token为空,用户未登录
                CurrentSysUser.removeCurrentSysUser();
                response.sendRedirect("login");
                return false;
            }
            String userId = redis.get(retValue);
            if (userId == null) {
                // operator为空,用户登录超时
                CurrentSysUser.removeCurrentSysUser();
                response.sendRedirect("login");
                return false;
            }
            SysUser sysUser = new SysUser();
            BeanUtils.copyProperties(sysUser, operator);
            sysUser.setUserAccount(operator.getUserCode());
            sysUser.setCompanyId(operator.getCompanyCode());
            sysUser.setCompanyName(operator.getCompanyName());
            sysUser.setSysPositionId(operator.getRoleCode());
            sysUser.setSysPositionName(operator.getRoleName());
            sysUser.setOrgId(operator.getOrgCode());
            RoleWorkData workData = operator.getRole().getWorkData();
            HashSet<String> dataSet = workData.getStrDataSet(RoleWorkData.DATATYPE_DEMAND_SIDE);
            sysUser.setDemandSide(dataSet);
            sysUser.setSaleChannel(workData.getStrDataSet(RoleWorkData.DATATYPE_SALE_CHANNEL));
            sysUser.setPopShopCodes(workData.getStrDataSet(RoleWorkData.DATATYPE_POP_SHOP_CODE));
            // 用户已登录
            CurrentSysUser.setCurrentSysUser(sysUser);
            return true;
        }
    
        @Override
        public void postHandle(HttpServletRequest request,
                               HttpServletResponse response, Object handler,
                               ModelAndView modelAndView) throws Exception {
        }
    
        @Override
        public void afterCompletion(HttpServletRequest request,
                                    HttpServletResponse response, Object handler, Exception ex)
                throws Exception {
            if (ex != null) {
                response.setStatus(500);
                logger.error("system.error", ex);
            }
            CurrentSysUser.removeCurrentSysUser();
        }
    
    }
    
    
    public class CurrentSysUser {
    	
    	
    	private static ThreadLocal<SysUser> sysUsers = new ThreadLocal<SysUser>();
    	
    	public static void setCurrentSysUser(SysUser sysUser){
    		sysUsers.set(sysUser);
    	}
    	
    	public static SysUser getCurrentSysUser(){
    		return sysUsers.get();
    	}
    	
    	public static void removeCurrentSysUser(){
    		sysUsers.remove();
    	}
    	
    	public static String getCurrentSysUserAccount(){
    		return sysUsers.get().getUserAccount();
    	}
    }
    
  • 相关阅读:
    Tomcat虚拟目录的映射方式
    Linux常用命令
    java断点调试
    破解MyEclipse
    JS判断浏览器
    css3 box-sizing详解。
    this-使用call . apply
    this-内部函数
    this-对象方法调用
    this-纯函数
  • 原文地址:https://www.cnblogs.com/stubborn-dude/p/14719208.html
Copyright © 2020-2023  润新知