ThreadLocal使用|内存泄漏

ThreadLocal的作用就是在统一线程的不同方法之间共享数据,是以一种简单地方式获取,在某些场合大大的便利了编程。
​​​​​​​ 

先来分析下类的聚合关系,每个Thread类中有一个ThreadLocalMap类,ThreadLocalMap维护着一个<ThreadLocal<?>, Object>类型的数组,你每次调用ThreadLocal的set()方法时,是将数据存入Thread的ThreadLocalMap中,每次调用ThreadLocal的get()方法,是从Thread的ThreadLocalMap中取数据,可以看看ThreadLocal的set()方法实现:

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

看到了吧,首先得到当前Thread的ThreadLocalMap,然后以ThreadLocal<T> 为key存入进去,ThreadLocal的get()方法也大同小异:

public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

这里不用考虑线程安全的问题,因为get()和set()是函数,且没有访问全局资源,这样是将临界资源直接存到所有调用线程的Thread类中,这种方式可以解决一部分情况下的多线程同步问题。

接下来我们来研究下ThreadLocal中弱引用的问题,Entry是ThreadLocalMap中存放调用ThreadLocal的set()方法时设置值的结构,我们看到Entry继承了WeakReference<?>,这也是我们使用WeakReference的常用方式,这里的Entry类中key是弱引用类型,当调用ThreadLocal的set(T value)方法时,不仅传递了value,还传递了ThreadLoal实例进去作为弱引用,需要强调的是一个ThreadLocal实例只能往一个线程中保存一个对象,想要保存多个的话,需要定义多个ThreadLocal实例,所以这里的一个Entry对应着某个线程里的一个ThreadLocal实例,这里的Entry中对ThreadLocal的引用是弱引用,但是Thread的ThreadLocalMap中对Entry是强引用,且Thread对ThreadLocalMap也是强引用,外层ThreadLocal实例也通常是持有强引用,所以,当外层的ThreadLocal被回收后,Entry#get()==null就会为true,Entry就知道可以将map中的这对值删除回收了,回收的时机是调用remove()、get()、set()时,所以,这个弱引用就是这个作用了,可以得知外层ThreadLocal的引用状况而回收map中键值对。

static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;

    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}

接下来我们说说ThreadLocal使用时可能触发的内存泄露问题,为什么会这样呢?主要原因还是线程生命周期过长,没有正确清理set(T value)方法设置进去的value值,如果线程是运行完毕即结束的就不会有内存泄漏的问题,ThreadLocal导致的内存泄漏多是在线程池中出现的,所以使用时注意一点,,有条件调用remove()方法的一定调用。这里我们也可以谈谈内存泄漏的原因,可以确认的是,Entry是继承了WeakReference的,实例化WeakReference泛型给的实例是ThreadLocal类型的当前实例,也就是说,当ThreadLocal实例没有强引用时,Entry里的这个ThreadLocal实例就会变成null,这时,ThreadLocalMap就可以回收这个Entry了,但是这个回收是需要手动调用set(),get(),remove()的,所以在没有调用这些函数的情况下就会出现内存泄漏,需要说明的是,内存泄漏不是使用弱引用引起的,相反,弱引用一定程度上是为了解决内存泄漏的,正是使用了弱引用,才能在其他ThreadLocal调用set(),get(),remove()方法的时候清理掉生命周期结束的ThreadLocal对应的Value。官方的建议是将ThreadLocal用static修饰,这样ThreadLocal就一直可达,你可以随时remove()清空掉Value,这种解决方案像是在玩文字游戏,在甩锅,如果你不用static导致内存泄漏使应用内存溢出,那是设计者的锅,现在你用static了,不存在内存泄漏,但是还是内存溢出了,是你代码有问题,是使用者的锅了。

另外来说下我们通常的用法,很多情况下,我们是将ThreadLocal封装一层后使用的,下面举个RocketMQ中使用的实例

public class ThreadLocalIndex {
    private final ThreadLocal<Integer> threadLocalIndex = new ThreadLocal<Integer>();
    private final Random random = new Random();

    public int getAndIncrement() {
        Integer index = this.threadLocalIndex.get();
        if (null == index) {
            index = Math.abs(random.nextInt());
            if (index < 0)
                index = 0;
            this.threadLocalIndex.set(index);
        }

        index = Math.abs(index + 1);
        if (index < 0)
            index = 0;

        this.threadLocalIndex.set(index);
        return index;
    }

    @Override
    public String toString() {
        return "ThreadLocalIndex{" +
            "threadLocalIndex=" + threadLocalIndex.get() +
            '}';
    }
}

这么封装的原因是业务需求导致的,有时我们使用ThreadLocal是需要一点业务逻辑的,并不仅仅是往里面带个参数那么简单。下面我们看看Spring中使用ThreadLocal的一个例子:

public abstract class RequestContextHolder  {

	private static final boolean jsfPresent =
			ClassUtils.isPresent("javax.faces.context.FacesContext", RequestContextHolder.class.getClassLoader());

	private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
			new NamedThreadLocal<RequestAttributes>("Request attributes");

	private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder =
			new NamedInheritableThreadLocal<RequestAttributes>("Request context");


	/**
	 * Reset the RequestAttributes for the current thread.
	 */
	public static void resetRequestAttributes() {
		requestAttributesHolder.remove();
		inheritableRequestAttributesHolder.remove();
	}

	/**
	 * Bind the given RequestAttributes to the current thread,
	 * <i>not</i> exposing it as inheritable for child threads.
	 * @param attributes the RequestAttributes to expose
	 * @see #setRequestAttributes(RequestAttributes, boolean)
	 */
	public static void setRequestAttributes(RequestAttributes attributes) {
		setRequestAttributes(attributes, false);
	}

	/**
	 * Bind the given RequestAttributes to the current thread.
	 * @param attributes the RequestAttributes to expose,
	 * or {@code null} to reset the thread-bound context
	 * @param inheritable whether to expose the RequestAttributes as inheritable
	 * for child threads (using an {@link InheritableThreadLocal})
	 */
	public static void setRequestAttributes(RequestAttributes attributes, boolean inheritable) {
		if (attributes == null) {
			resetRequestAttributes();
		}
		else {
			if (inheritable) {
				inheritableRequestAttributesHolder.set(attributes);
				requestAttributesHolder.remove();
			}
			else {
				requestAttributesHolder.set(attributes);
				inheritableRequestAttributesHolder.remove();
			}
		}
	}

	/**
	 * Return the RequestAttributes currently bound to the thread.
	 * @return the RequestAttributes currently bound to the thread,
	 * or {@code null} if none bound
	 */
	public static RequestAttributes getRequestAttributes() {
		RequestAttributes attributes = requestAttributesHolder.get();
		if (attributes == null) {
			attributes = inheritableRequestAttributesHolder.get();
		}
		return attributes;
	}

	/**
	 * Return the RequestAttributes currently bound to the thread.
	 * <p>Exposes the previously bound RequestAttributes instance, if any.
	 * Falls back to the current JSF FacesContext, if any.
	 * @return the RequestAttributes currently bound to the thread
	 * @throws IllegalStateException if no RequestAttributes object
	 * is bound to the current thread
	 * @see #setRequestAttributes
	 * @see ServletRequestAttributes
	 * @see FacesRequestAttributes
	 * @see javax.faces.context.FacesContext#getCurrentInstance()
	 */
	public static RequestAttributes currentRequestAttributes() throws IllegalStateException {
		RequestAttributes attributes = getRequestAttributes();
		if (attributes == null) {
			if (jsfPresent) {
				attributes = FacesRequestAttributesFactory.getFacesRequestAttributes();
			}
			if (attributes == null) {
				throw new IllegalStateException("No thread-bound request found: " +
						"Are you referring to request attributes outside of an actual web request, " +
						"or processing a request outside of the originally receiving thread? " +
						"If you are actually operating within a web request and still receive this message, " +
						"your code is probably running outside of DispatcherServlet/DispatcherPortlet: " +
						"In this case, use RequestContextListener or RequestContextFilter to expose the current request.");
			}
		}
		return attributes;
	}


	/**
	 * Inner class to avoid hard-coded JSF dependency.
 	 */
	private static class FacesRequestAttributesFactory {

		public static RequestAttributes getFacesRequestAttributes() {
			FacesContext facesContext = FacesContext.getCurrentInstance();
			return (facesContext != null ? new FacesRequestAttributes(facesContext) : null);
		}
	}

}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值