ThreadLocal原理解析
ThreadLocal叫做线程本地变量,或者线程局部变量。ThreadLocal为变量在每一个线程中都创建一个一个副本,每个线程可以访问自己的副本。
下面来看看ThreadLocal的实现原理。
下面的是ThreadLocal类提供的几个方法:
public T get();
public void set(T value);
public void remove();
protected T initialValue();
get()方法是用来获取ThreadLocal在当前线程中保存的变量,set()方法用来设置当前线程中变量的副本,remove()用来移除当前线程中变量的副本,initialValue()是一个protected方法,一般是用来在使用的时候重写。
下面的是get()方法的实现:
public T get() {
// 先获取当前的线程
Thread t = Thread.currentThread();
// 然后使用当前线程作为参数获取一个map,这个map的类型为ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null) {
// 如果map不为空,那么从这个map中获取这个键值对
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
// 调用setInitialValue()方法返回value。
return setInitialValue();
}
下面来看下getMap()方法做了什么。
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
在getMap()中调用当前线程t,返回当前线程中的一个成员变量threadLocals。
threadLocals是ThreadA中的一个变量,如下:
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
而ThreadLocalMap,这个类型是ThreadLocal类的一个内部类,下面的是ThreadLocalMap的源码:
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;
}
}
}
从上面可以看到ThreadLocalMap的Entry集成了WeakReference,并使用了ThreadLocal作为键值。
下面的是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;
}
如果map为空,就设置键值对,为空,再创建Map,看一下createMap的实现:
/**
* Create the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @param firstValue value for the initial entry of the map
*/
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
首先,在每一个线程Thread内部都有一个ThreadLocal.ThreadLocalMap类型的成员变量threadLocals,这个threadLocals就是用来存储实际变量的副本的,键值为当前ThreadLocal变量,value为变量的副本。
下面的是一个ThreadLocal使用的例子:
public class ThreadLocalTest {
ThreadLocal<String> strLocal = new ThreadLocal<>();
public void set() {
strLocal.set(Thread.currentThread().getName());
}
public String get() {
return strLocal.get();
}
public static void main(String[] args) throws InterruptedException {
ThreadLocalTest test = new ThreadLocalTest();
test.set();
System.out.println(test.get());
Thread thread = new Thread(() -> {
test.set();
System.out.println(test.get());
});
thread.start();
thread.join();
System.out.println(test.get());
}
}
Thread使用技巧:使用ThreadLocal创建一个一个线程只有一个实例的类
public class OnlyOneInOneThread {
private static ThreadLocal<OnlyOneInOneThread> oneInOneThreadThreadLocal;
private OnlyOneInOneThread() {
}
public static void onlyOneInOneThread() throws Exception {
if (oneInOneThreadThreadLocal.get() != null) {
throw new Exception("This thread has One!");
}
oneInOneThreadThreadLocal.set(new OnlyOneInOneThread());
}
public static OnlyOneInOneThread getOnlyOneInOneThread() {
return oneInOneThreadThreadLocal.get().;
}
}
上面的代码创建了一个线程只有一个实例的类,在一个线程中只能调用一次onlyOneInOneThread()方法,当再次调用时就会报错。当在某些特殊场合的时候,会需要一个线程只有有一个实例的情况。
ThreadLocal引发的问题
在了解ThreadLocal的实现后,会有一个问题。那就是如果线程不退出那么Thread类内部的ThreadLocalMap中的东西将一直存在。
当线程退出的时候,Thread类会进行一些清理工作,其中就包括ThreadLocalMap,Thread退出的代码如下:
/**
* 在线程退出前,有系统进行回调,进行资源清理
*/
private void exit() {
if (group != null) {
group.threadTerminated(this);
group = null;
}
/* Aggressively null out all reference fields: see bug 4006245 */
target = null;
/* Speed the release of some of these resources */
// ThreadLocalMap加速清理
threadLocals = null;
inheritableThreadLocals = null;
inheritedAccessControlContext = null;
blocker = null;
uncaughtExceptionHandler = null;
}
但是,我们在使用线程池的时,当前的线程未必会退出,例如:固定大小的线程池,线程总是存在。这样,如果将一些较大的对象存储在ThreadLocalMap中,并且不清除的时候,那么这些东西就会造成内存浪费。而且有时会存在由于这个变量没有被清除而导致线程池在下一次使用这个线程执行任务的时候而产生的一些不容易被发现的问题。
那么如果想要及时的回收对象,最好使用ThreadLocal.remove()方法将这个变量移除。