threadLoacl原理和源码 http://www.jasongj.com/java/threadlocal/
ThreadLocal具体原理
Thread维护ThreadLocal与实例的映射
Thread 拥有 ThreadLocal.ThreadLocalMap 变量;
线程访问ThreadLocal变量,只有通过ThreadLocal对象本身才能能够去ThreadLocalMap取出ThreadLocal变量的值;
// -----------------ThreadLocal.class-----------------
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 getMap(Thread t) {
return t.threadLocals;
}
// -----------------Thread.class-----------------
ThreadLocal.ThreadLocalMap threadLocals = null;
|
对于Thread 的 ThreadLocalMap 变量 , 它使用一个entry数组维护所有的ThreadLocal变量。
int i = key.threadLocalHashCode & (table.length - 1); Entry e = table[i];
// -----------------ThreadLocalMap.class--------------------
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);
}
|
ThreadLocal维护线程与实例的映射(错误)
这是一个抽象概念。threadLocal只是拥有一个拥有数据ThreadlocalMap内部类,但是真正拥有ThreadlocalMap的是thread;threalocal只是通过这个ThreadlocalMap访问数据而已;
这个图是错的。
个人代码示例
打印的值,体现了threadlocal是线程私有的;但是不同值的threadlocal 的 hashcode的一致性,又印证了上面的理论;
threadlocal 类似 一个 去 threadlocalMap 取出value 的一把钥匙; 而并非threadlocal本身 有一个<key, value> 为 <thread, threadlocal_local>的map;
// 1 [email protected] ---1 // 3 [email protected] null // 2 [email protected] null public static void main(String args[]) throws Exception { Thread a = new Thread(new Runnable(){ @Override public void run(){ TestThreadLocal test = new TestThreadLocal(); test.set("---1"); test.println("1 " + TestThreadLocal.threadLocal); } }); Thread b = new Thread(new Runnable(){ @Override public void run(){ TestThreadLocal test = new TestThreadLocal(); // test.set("2 "); test.println("2 " + TestThreadLocal.threadLocal); } }); a.start(); b.start(); TestThreadLocal demo = new TestThreadLocal(); demo.println("3 " + TestThreadLocal.threadLocal); } public static class TestThreadLocal { public static ThreadLocal<String> threadLocal = new ThreadLocal<>(); public void set(String str) { this.threadLocal.set(str); } public String get() { return this.threadLocal.get(); } public void println(String flag) { System.out.println(flag + " " + get()); //append 暂时不用了 } // @Override public void run() { String threadName = Thread.currentThread().getName(); set(threadName); println(threadName); } } } |
ThreadLocalMap与内存泄漏
该方案中,Map 由 ThreadLocal 类的静态内部类 ThreadLocalMap 提供。该类的实例维护某个 ThreadLocal 与具体实例的映射。与 HashMap 不同的是,ThreadLocalMap 的每个 Entry 都是一个对 键 的弱引用,这一点从super(k)可看出。另外,每个 Entry 都包含了一个对 值 的强引用。
|
1 2 3 4 5 6 7 8 9 |
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
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);
}
|
ThreadLocal.ThreadLocalMap.entry 使用弱引用;
弱引用优点:
使用弱引用的原因在于,当没有强引用指向 ThreadLocal 变量时,Object对象(key)可被回收,从而避免上文所述 ThreadLocal 不能被回收而造成的内存泄漏的问题。
弱引用缺点:
但是,这里又可能出现另外一种内存泄漏的问题。ThreadLocalMap 维护 ThreadLocal 变量与具体实例的映射,当 ThreadLocal 变量被回收后,该映射的键变为 null,该 Entry 无法被移除。从而使得实例被该 Entry 引用而无法被回收造成内存泄漏。
注:Entry虽然是弱引用,但它是 ThreadLocal 类型的弱引用(也即上文所述它是对 键 的弱引用),而非具体实例的的弱引用,所以无法避免具体实例相关的内存泄漏。
防止内存泄漏
对于已经不再被使用且已被回收的 ThreadLocal 对象,它在每个线程内对应的实例由于被线程的 ThreadLocalMap 的 Entry 强引用,无法被回收,可能会造成内存泄漏。
针对该问题,ThreadLocalMap 的 set 方法中,通过 replaceStaleEntry 方法将所有键为 null 的 Entry 的值设置为 null,从而使得该值可被回收。另外,会在 rehash 方法中通过 expungeStaleEntry 方法将键和值为 null 的 Entry 设置为 null 从而使得该 Entry 可被回收。通过这种方式,ThreadLocal 可防止内存泄漏。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
private void set(ThreadLocal<?> key, Object value) {
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 适用于如下两种场景
- 每个线程需要有自己单独的实例
- 实例需要在多个方法中共享,但不希望被多线程共享
对于第一点,每个线程拥有自己实例,实现它的方式很多。例如可以在线程内部构建一个单独的实例。ThreadLoca 可以以非常方便的形式满足该需求。
对于第二点,可以在满足第一点(每个线程有自己的实例)的条件下,通过方法间引用传递的形式实现。ThreadLocal 使得代码耦合度更低,且实现更优雅。