ThreadLocal
ThreadLocal,很多地方叫做线程本地变量,也有些地方叫做线程本地存储,其实意思差不多。可能很多朋友都知道ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。
ThreadLocal 的作用和同步机制有些相反:同步机制是为了保证多线程环境下数据的一致性;而 ThreadLocal 是保证了多线程环境下数据的独立性。
ThreadLocal源码分析
分析重点:
public T get() { }
public void set(T value) { }
public void remove() { }
protected T initialValue() { }
static class ThreadLocalMap { }
void createMap(Thread t, T firstValue) { }
ThreadLocal.ThreadLocalMap threadLocals;
get()方法是用来获取ThreadLocal在当前线程中保存的变量副本,set()用来设置当前线程中变量的副本,remove()用来移除当前线程中变量的副本,initialValue()是一个protected方法,一般是用来在使用时进行重写的,它是一个延迟加载方法,ThreadLocalMap是内部类,用来保存变量副本;
ThreadLocal类为每个线程创建一个变量的副本集合(threadLocals),我们可以通过threadLocal调用上面的方法来操作其对应的变量副本;
get方法:
/**
* Returns the value in the current thread's copy of this
* thread-local variable. If the variable has no value for the
* current thread, it is first initialized to the value returned
* by an invocation of the {@link #initialValue} method.
*
* @return the current thread's value of this thread-local
*/
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value;
}
return setInitialValue();
}
该方法会获取当前线程,然后通过getMap方法去获取当前线程的变量的副本集合,然后从集合从获取对应threadLocal的副本变量值,这里获取键值传入的this指的就是调用get方法的threadLocal实例;如果集合中不存在对应的副本变量,则进行延迟加载;延迟加载的方法需要我们重写,所以在没有重写initialValue方法的情况下,需要先调用set方法再调用get方法,不然所以线程都只会返回null,具体原因后面将进行分析和实例展示;
set方法:
/**
* Sets the current thread's copy of this thread-local variable
* to the specified value. Most subclasses will have no need to
* override this method, relying solely on the {@link #initialValue}
* method to set the values of thread-locals.
*
* @param value the value to be stored in the current thread's copy of
* this thread-local.
*/
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
set方法会对map集合进行初始化并赋值;
remove方法:
/**
* Removes the current thread's value for this thread-local
* variable. If this thread-local variable is subsequently
* {@linkplain #get read} by the current thread, its value will be
* reinitialized by invoking its {@link #initialValue} method,
* unless its value is {@linkplain #set set} by the current thread
* in the interim. This may result in multiple invocations of the
* <tt>initialValue</tt> method in the current thread.
*
* @since 1.5
*/
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
remove方法会从map集合中删除对应的threadLocal变量副本;
initialValue方法:
/**
* Returns the current thread's "initial value" for this
* thread-local variable. This method will be invoked the first
* time a thread accesses the variable with the {@link #get}
* method, unless the thread previously invoked the {@link #set}
* method, in which case the <tt>initialValue</tt> method will not
* be invoked for the thread. Normally, this method is invoked at
* most once per thread, but it may be invoked again in case of
* subsequent invocations of {@link #remove} followed by {@link #get}.
*
* <p>This implementation simply returns <tt>null</tt>; if the
* programmer desires thread-local variables to have an initial
* value other than <tt>null</tt>, <tt>ThreadLocal</tt> must be
* subclassed, and this method overridden. Typically, an
* anonymous inner class will be used.
*
* @return the initial value for this thread-local
*/
protected T initialValue() {
return null;
}
initialValue方法默认返回的是null,在实际使用中需要我们重写该方法,这也是我们前面提到在调用get方法之前没有调用set方法也没有重写initialValue方法会所有线程都返回null的原因,下面我们结合源码来分析:
首先我们知道直接调用get方法的话,map中不存在副本变量,所以就会调用setInitialValue方法:
/**
* Variant of set() to establish initialValue. Used instead
* of set() in case user has overridden the set() method.
*
* @return the initial value
*/
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
此时T value = initialValue()调用的返回值为null,也就是返回的value为null,所以所有的get方法的返回值都为nul;
调用get方法之前没有调用set方法也没有重写initialValue方法的实例代码:
public class ThreadLocalTest {
ThreadLocal<Long> longThreadLocal=new ThreadLocal<Long>();
ThreadLocal<String> stringThreadLocal=new ThreadLocal<String>();
public static void main(String[] args) throws InterruptedException {
final ThreadLocalTest test=new ThreadLocalTest();
System.out.println(test.longThreadLocal.get());
System.out.println(test.stringThreadLocal.get());
Thread thread=new Thread(new Runnable() {
@Override
public void run() {
System.out.println(test.longThreadLocal.get());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(test.stringThreadLocal.get());
}
});
thread.start();
thread.join();
System.out.println(test.longThreadLocal.get());
System.out.println(test.stringThreadLocal.get());
}
}
运行结果:
重写initialValue的实例代码:
public class ThreadLocalTest {
ThreadLocal<Long> longThreadLocal=new ThreadLocal<Long>(){
@Override
protected Long initialValue() {
return Thread.currentThread().getId();
}
};
ThreadLocal<String> stringThreadLocal=new ThreadLocal<String>(){
@Override
protected String initialValue() {
return Thread.currentThread().getName();
}
};
public static void main(String[] args) throws InterruptedException {
final ThreadLocalTest test=new ThreadLocalTest();
System.out.println(test.longThreadLocal.get());
System.out.println(test.stringThreadLocal.get());
Thread thread=new Thread(new Runnable() {
@Override
public void run() {
System.out.println(test.longThreadLocal.get());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(test.stringThreadLocal.get());
}
});
thread.start();
thread.join();
System.out.println(test.longThreadLocal.get());
System.out.println(test.stringThreadLocal.get());
}
}
运行结果:
ThreadLocal的内存泄露问题
threadlocal里面使用了一个存在弱引用的map,当释放掉threadlocal的强引用以后,map里面的value却没有被回收.而这块value永远不会被访问到了. 所以存在着内存泄露. 最好的做法是将调用threadlocal的remove方法。
static class ThreadLocalMap {
/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
}
在threadlocal的生命周期中,都存在这些引用. 看下图: 实线代表强引用,虚线代表弱引用。
如上图,ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal没有外部强引用引用他,那么系统gc的时候,这个ThreadLocal势必会被回收,这样一来,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,这些key为null的Entry的value就会一直存在一条强引用链:ThreadLocal Ref -> Thread -> ThreaLocalMap -> Entry -> value ;这就是我们认为的内存泄露,只有当前thread结束以后, current thread就不会存在栈中,强引用断开, Current Thread, Map, value将全部被GC回收。
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
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();
}
Java为了最小化减少内存泄露的可能性和影响,在ThreadLocal的get,set的时候都会清除线程Map里所有key为null的value。但是还是会出现这种情况:threadLocal对象设null了,开始发生“内存泄露”,然后使用线程池,这个线程结束,线程放回线程池中不销毁,这个线程一直不被使用,或者分配使用了又不再调用get,set方法,那么这个期间就会发生真正的内存泄露。
很多情况下需要使用者手动调用ThreadLocal的remove函数,手动删除不再需要的ThreadLocal,防止内存泄露。所以JDK建议将ThreadLocal变量定义成private static的,这样的话ThreadLocal的生命周期就更长,由于一直存在ThreadLocal的强引用,所以ThreadLocal也就不会被回收,也就能保证任何时候都能根据ThreadLocal的弱引用访问到Entry的value值,然后remove它,防止内存泄露。