先看JDK关于ThreadLocal的类注释:web
This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).
翻译过来大概的意思为:ThreadLocal提供了线程内部的局部变量;每一个线程都有本身的,独立的初始化变量副本;ThreadLocal实例一般是类中的private static字段,该类通常在线程状态相关(或线程上下文)中使用。数据库
Each thread holds an implicit reference to its copy of a thread-local variable as long as the thread is alive and the ThreadLocal instance is accessible; after a thread goes away, all of its copies of thread-local instances are subject to garbage collection (unless other references to these copies exist).
翻译过来大概的意思为:只要线程处于活动状态且ThreadLocal实例是可访问的状态下,每一个线程都持有对其线程局部变量副本的隐式引用;在线程消亡后,ThreadLocal实例s的全部副本都将进行垃圾回收(除非存在对这些副本的其余引用)。数组
一、多线程下使用日志追踪,如Logback或Log4j的MDC组件
二、在事务中,connection绑定到当前线程来保证这个线程中的数据库操做用的是同一个connection
三、dubbo的RpcContext的实现:<RpcContext是一个临时状态记录器,当接收到RPC请求,或发起RPC请求时,RpcContext的状态都会变化>
四、如今的分布式trace系统中的traceId、spanId的传递等
五、web前台的请求参数,在同一线程内多个方法之间隐式传递
。。。缓存
一个简单的demo:多线程
public class TraceContext { private static final ThreadLocal<String> traceIdHolder = new ThreadLocal<String>() { @Override protected String initialValue() {//① return UUID.randomUUID().toString().replaceAll("-", ""); } }; public static void setTraceId(String traceId) { traceIdHolder.set(traceId);//② } public static String getTraceId() { return traceIdHolder.get();//③ } public static void removeTraceId() { traceIdHolder.remove();//④ } }
思考:ThreadLocal类型的traceIdHolder通常被修饰为static、final、private,就是traceIdHolder在被使用的时候为单例不可变(这不是常见的单例饱汉模式么)。若是traceIdHolder定义为多实例会怎么样?less
如下以JDK1.8实现解读dom
ThreadLocal的构造函数为空:public ThreadLocal() {}
分布式
ThreadLocal的set方法:ide
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } ThreadLocalMap getMap(Thread t) { return t.threadLocals; }
获取当前线程的ThreadLocalMap,有直接设置value、没有新建
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(); }
从当前线程的ThreadLocalMap中查找Entry,若是没必要为null返回value,不然设置初值并返回setInitialValue()
ThreadLocal的remove()方法:
public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); }
从当前线程的ThreadLocalMap中删除
ThreadLocal的setInitialValue()方法:
private T setInitialValue() { T value = initialValue();//未覆盖就是null Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); return value; }
和set相似
查看Thread的threadLocals的字段定义:ThreadLocal.ThreadLocalMap threadLocals = null;
查看ThreadLocal的内部类ThreadLocalMap的定义:
虽然ThreadLocalMap命名含有'Map',但和Map接口没任何关系。ThreadLocalMap底层是一个的散列表(可扩容的数组),并采用开放地址法来解决hash冲突。
ThreadLocalMap.Entry定义:
static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } }
这里先无论为啥使用WeakReference定义。稍后讨论
ThreadLocalMap.set方法:
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(); }
每一个ThreadLocal对象都有一个hash值threadLocalHashCode,每初始化一个ThreadLocal对象,hash值就增长一个固定的大小0x61c88647。
定义以下:
private final int threadLocalHashCode = nextHashCode(); /** * The next hash code to be given out. Updated atomically. Starts at * zero. */ private static AtomicInteger nextHashCode = new AtomicInteger(); /** * The difference between successively generated hash codes - turns * implicit sequential thread-local IDs into near-optimally spread * multiplicative hash values for power-of-two-sized tables. */ private static final int HASH_INCREMENT = 0x61c88647; /** * Returns the next hash code. */ private static int nextHashCode() { return nextHashCode.getAndAdd(HASH_INCREMENT); }
ThreadLocalMap.set流程总结以下:
ThreadLocalMap.getEntry方法:
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 Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) { Entry[] tab = table; int len = tab.length; while (e != null) { ThreadLocal<?> k = e.get(); if (k == key) return e; if (k == null) expungeStaleEntry(i); else i = nextIndex(i, len); e = tab[i]; } return null; } private int expungeStaleEntry(int staleSlot) { Entry[] tab = table; int len = tab.length; // expunge entry at staleSlot tab[staleSlot].value = null; tab[staleSlot] = null; size--; // Rehash until we encounter null Entry e; int i; for (i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) { ThreadLocal<?> k = e.get(); if (k == null) { e.value = null; tab[i] = null; size--; } else { int h = k.threadLocalHashCode & (len - 1); if (h != i) { tab[i] = null; // Unlike Knuth 6.4 Algorithm R, we must scan until // null because multiple entries could have been stale. while (tab[h] != null) h = nextIndex(h, len); tab[h] = e; } } } return i; }
ThreadLocalMap.getEntry流程总结以下:
ThreadLocalMap.remove方法:
private void remove(ThreadLocal<?> key) { 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)]) { if (e.get() == key) { e.clear(); expungeStaleEntry(i); return; } } }
ThreadLocalMap.remove流程总结以下:
总结ThreadLocalMap:
Thread、ThreadLoal、ThreadLocalMap关系图:
总体的对象关系图
如下分析转自知乎做者winwill2012,连接:我以为是这样的
如上图,ThreadLocalMap使用ThreadLocal的弱引用做为key,若是一个ThreadLocal没有外部强引用引用他,那么系统gc的时候,这个ThreadLocal势必会被回收,这样一来,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,若是当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链:
ThreadRef -> Thread -> ThreaLocalMap -> Entry -> value
永远没法回收,形成内存泄露。
所以在使用ThreadLocal的时候要手动调用remove方法,防止内存泄漏。
JDK建议将ThreadLocal变量定义成private static的,这样的话ThreadLocal的生命周期就更长(类的静态属性引用的对象为GCRoots),因为一直存在ThreadLocal的强引用,因此ThreadLocal也就不会被回收,也就能保证任什么时候候都能根据ThreadLocal的弱引用访问到Entry的value值,而后remove它,防止内存泄露。
我觉的JDK建议将ThreadLocal变量定义成private static的还有个可能缘由是:单例,ThreadLocal对象是无状态的,无含义的,声明同一类型的ThreadLocal对象多实例,浪费ThreadLocalMap的存储空间且对象更容易引发内存泄漏。