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 java.lang.ThreadLocal@2a60c715 ---1 // 3 java.lang.ThreadLocal@2a60c715 null // 2 java.lang.ThreadLocal@2a60c715 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 使得代码耦合度更低,且实现更优雅。