ThreadLocal使用以及原理
Posted jinshuai86
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ThreadLocal使用以及原理相关的知识,希望对你有一定的参考价值。
介绍
- ThreadLocal是一个用于创建线程局部变量的类。当前线程通过ThreadLocal的set()方法设置的变量只对当前线程可见,通过get()获取设置的变量。
使用
- 支持泛型
ThreadLocal<String> threadLocal = new ThreadLocal<>();
- 当前线程通过ThreadLocal对象的set(value)/get()设置变量和获取设置的变量
threadLocal.set("jinshuai");
threadLocal.get();
原理
- 每个线程Thread维护一个ThreadLocalMap<key,value>
- key是ThreadLocal对象,value是通过set(value)设置的值
- key是ThreadLocal对象,value是通过set(value)设置的值
- 当前线程调用threadLocal.set("jinshuai");
- 首先获取当前线程所维护的ThreadLocalMap
- 然后判断当前线程是否已经创建过这个ThreadLocalMap
- 如果已经创建,会将ThreadLocal对象当作key,和当前线程要设置的值当作value放到ThreadLocalMap。
- 如果没有创建,会创建并初始化一个ThreadLocalMap(类似HashMap 初始化数组长度为2的幂,设置扩充阈值...)然后同上↑
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
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对象
private static final ThreadLocal<SimpleDateFormat> formatter = new ThreadLocal<SimpleDateFormat>(){
@Override
protected SimpleDateFormat initialValue()
{
return new SimpleDateFormat("yyyyMMdd HHmm");
}
};
public String formatIt(Date date)
{
return formatter.get().format(date);
}
- 当某一个变量比如User对象在多个方法中传递时,会变得比较乱此时可以通过ThreadLocal设置变量
比如在Servlet中:
doGet(HttpServletRequest req, HttpServletResponse resp) { User user = getLoggedInUser(req); doSomething(user) doSomethingElse(user) renderResponse(resp,user) }
每个方法都需要一个user,不够优雅(咳咳...),此时可以通过设置一个ThreadLocal单例,然后set(user):
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()移除设定的值,防止内存泄漏
// 会移除这个Entry
threadLocal.remove();
- 在线程池中由于线程会被复用,所以不会停止,导致每个线程的ThreadLocalMap里的key是ThreadLocal的弱引用,GC时会将其回收,但是其对应的value一直有一个强引用不会被回收造成内存泄漏。
/**
* 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;
}
}
参考
- https://droidyue.com/blog/2016/03/13/learning-threadlocal-in-java/
- http://blog.xiaohansong.com/2016/08/06/ThreadLocal-memory-leak/#
- https://stackoverflow.com/questions/817856/when-and-how-should-i-use-a-threadlocal-variable
- https://stackoverflow.com/questions/1490919/purpose-of-threadlocal
以上是关于ThreadLocal使用以及原理的主要内容,如果未能解决你的问题,请参考以下文章
聊聊ThreadLocal原理以及使用场景-JAVA 8源码
一篇文章看懂 ThreadLocal 原理,内存泄露,缺点以及线程池复用的值传递问题