ThreadLocal应用及原理

ThreadLocal是一个用于创建线程局部变量的类。当前线程通过ThreadLocal的set()方法设置的变量只对当前线程可见,通过get()获取设置的变量。

使用

  • 支持泛型

    1
    ThreadLocal<String> threadLocal = new ThreadLocal<>();
  • 当前线程通过ThreadLocal对象的set(value)/get()设置变量和获取设置的变量

    1
    2
    threadLocal.set("jinshuai");
    threadLocal.get();

原理

  • 每个线程Thread维护一个ThreadLocalMap<key,value>
    • key是ThreadLocal对象,value是通过set(value)设置的值
  • 当前线程调用threadLocal.set(“jinshuai”);
    • 首先获取当前线程所维护的ThreadLocalMap
    • 然后判断当前线程是否已经创建过这个ThreadLocalMap
      • 如果已经创建,会将ThreadLocal对象当作key,和当前线程要设置的值当作value放到ThreadLocalMap。
      • 如果没有创建,会创建并初始化一个ThreadLocalMap(类似HashMap 初始化数组长度为2的幂,设置扩充阈值…)然后同上↑
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void set(T value) {
// 获取当前线程对象
Thread t = Thread.currentThread();
// 获取线程的ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
// 获取ThreadLocalMap
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
  • 当前线程调用threadLocal.get();
    • 首先获取当前线程所维护的ThereadLocalMap
    • 然后将ThreadLocal对象作为key获取对应的Entry
      • 如果Entry不为空获取Entry的value
      • 如果Entry为空直接返回一个setInitvalue()值也就是null
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public T get() {
// 获取当前线程对象
Thread t = Thread.currentThread();
// 获取当前线程对应的ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null) {
// 将ThreadLocal(this)作为key获取entry
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
// 获取entry的value
T result = (T)e.value;
return result;
}
}
// 如果entry为空
return setInitialValue();
}

应用场景

  • 如果一个对象非线程安全,但又不想通过加锁的方式实现线程安全,可以通过ThreadLocal.set()对象的值,比如SimpleDataFormat不是线程安全的,此时可以每个线程设置一个SimpleDataFormat对象
1
2
3
4
5
6
7
8
9
private static final ThreadLocal<SimpleDateFormat> formatter = new ThreadLocal<>(){
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("yyyyMMdd HHmm");
}
};
public String formatIt(Date date) {
return formatter.get().format(date);
}
  • 当某一个变量比如User对象在多个方法中传递时,会变得比较乱此时可以通过ThreadLocal设置变量
    • 比如在Servlet中:
1
2
3
4
5
6
doGet(HttpServletRequest req, HttpServletResponse resp) {
User user = getLoggedInUser(req);
doSomething(user)
doSomethingElse(user)
renderResponse(resp,user)
}
  • 每个方法都需要一个user,不够优雅(咳咳…),此时可以通过设置一个ThreadLocal单例,然后set(user):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
doGet(HttpServletRequest req, HttpServletResponse resp) {
User user = getLoggedInUser(req);
ThreadLocalSingleInstace.getThreadLocal().set(user)
try {
doSomething()
doSomethingElse()
renderResponse(resp)
} finally {
ThreadLocalSingleInstace.getThreadLocal().remove()
}
}
// 获取ThreadLocal单例
class ThreadLocalSingleInstace {
static private ThreadLocal threadLocal = new ThreadLocal<User>();
static ThreadLocal<User> getThreadLocal() {
return threadLocal;
}
}

注意

  • 用完以后应该调用remove()移除设定的值,防止内存泄漏
1
2
// 会移除这个Entry
threadLocal.remove();
  • 在线程池中由于线程会被复用,所以不会停止,导致每个线程中ThreadLocalMap里的key和虚引用Entry关联(Entry继承了虚引用),GC时会将key引用的对象回收,但是Entry中的value对象一直有一个强引用value不会被回收造成内存泄漏。
1
2
3
4
5
6
7
8
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}

参考