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);
}
}
}