ThreadLocal

本文详细介绍了ThreadLocal的工作原理,如何通过弱引用避免内存泄漏,以及在实际应用中的例子,如Spring框架中的RequestContextHolder。强调了在使用ThreadLocal后,务必手动调用remove方法以防止内存泄漏。
摘要由CSDN通过智能技术生成

使用场景
用来解决线程安全问题,如每个线程都有自己的SimpleDateFormat;还可以用来存储每个线程特有的内容,如用户信息,日志记录的traceID,省去传参的麻烦。

原理

在这里插入图片描述
如图:每个线程都有一个属性ThreadLocal.ThreadLocalMap threadLocals = null;明显它是map结构,key是ThreadlLocal本身(this),value是我们用户存储的值。每个线程可以存储多个ThreadLocal值

然后我们看ThreadLocal提供的几个方法,有get() set() remove() withInitial()方法。

 public void set(T value) {
 		//就是获取当前线程的ThreadLocalMap 
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        //为空就创建,否则直接设置值
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
 }

然后看get方法

public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
        	//key即ThreadLocal对应的hashCode是个new AtomicInteger().getAndAdd(0x61c88647),算法目的均匀分配
        	//这里面还会去删除为null的entry
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        //首次调用,就会执行我们重写的init方法设置初始值,否则返回默认null
        return setInitialValue();
    }

ThreadLocalMap是一个数组结构

static class ThreadLocalMap {
	//注意这里Entry 继承了WeakReference<ThreadLocal<?>
	 static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

        /**
         * The table, resized as necessary.
         * table.length MUST always be a power of two.
         */
        private Entry[] table;
}

当key冲突的时候,会寻找下一个空的位置。key即ThreadLocal对应的hashCode是个new AtomicInteger().getAndAdd(0x61c88647),目的是均匀分配。

ThreadLocal的内存泄漏

内存泄漏有两种情况,key的泄漏,或者value的泄漏。

从ThreadLocalMap 的Entry extends WeakReference<ThreadLocal<?>可以看出,key即ThreadLocal继承了弱引用,如果没有强引用指向这个key,也就是只有弱引用时,下次GC就会回收掉key。

正常情况,线程执行结束后,线程t被回收 ,他里面的key和value肯定也会被回收。但是如果线程是在线程池中,线程被重复利用,线程不会死亡,那他的属性ThreadLocalMap 包括里面的value就不会被回收(如果ThreadlLcoal实例置为null,那么key是弱引用会被回收),这时就存在内存泄漏。怎么解决呢?

实际上,Java已经考虑到这个问题,会在get、resize、remove方法中检测如果key为null的entry,会把value设置为null,避免了value被强引用造成内存泄漏。但是如果我们不使用ThreadLocal,也实际上就不会使用到他的get、resize、remove方法,线程又不终止调用链就会一直存在,这样就又导致了value的泄漏。所以我们在使用完ThreadLocal后,必须手动调用remove方法,如拦截器最后finally里调用remove方法。

spring已经为我们提供了很多,如DateTimeContextHolder,RequestContextHolder,尽量使用框架提供的

应用举例

Spring框架中最明显的就是RequestContextHolder,它保存了当前请求参数,常见用法如:

ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = requestAttributes.getRequest();
String authorization = request.getHeader("Authorization");

它包含了两个ThreadLocal<RequestAttributes>

public abstract class RequestContextHolder  {
	private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
			new NamedThreadLocal<>("Request attributes");
	// 需要开启才能在子线程中获取父线程的值
	private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder =
			new NamedInheritableThreadLocal<>("Request context");
}

注意Spring框架默认是在DispatcherServlet里(父类FrameworkServlet)的processRequest()里调用了RequestContextHolder.setRequestAttributes()方法来向当前线程的ThreadLocal放入ServletRequestAttributes,这样我们就可以在当前线程里使用RequestContextHolder.getRequestAttributes()了。

除了默认的DispatcherServlet,Spring框架也会在RequestContextListener和RequestContextFilter里去设置setRequestAttributes。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值