ThreadLocal 是线程局部变量,每个线程都有独立的一个变量。通过创建对象副本,达到线程安全的目的。 它并不是用来解决在多线程并发环境下资源共享问题的,而是用来提供线程内的局部变量,这样每个线程都自己管理自己的局部变量,别的线程操作的数据不会对当前线程产生影响。
示例:
1 2 3 4 5 6 7 8 public static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal = new ThreadLocal () { @Override protected SimpleDateFormat initialValue () { return new SimpleDateFormat ("yyyy-MM-dd HH:mm:ss" ); } }; SimpleDateFormat dateFormat = dateFormatThreadLocal.get();
同一个线程获取相同的实例,不同的线程获取不同的实例。
类结构 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class ThreadLocal <T> { private Entry[] table; public ThreadLocal () { } static class ThreadLocalMap { static class Entry extends WeakReference <ThreadLocal<?>> { Object value; Entry(ThreadLocal<?> k, Object v) { super (k); value = v; } } } }
主要方法 initialValue() 初始化 懒加载,在 get() 方法中调用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 private T setInitialValue () { T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null ) map.set(this , value); else createMap(t, value); return value; } protected T initialValue () { return null ; }
该方法每个线程最多调用一次。如果调用了 remove() 方法,再次调用 get() 时会再次初始化。
set() 设置局部变量 1 2 3 4 5 6 7 8 9 10 11 12 13 public void set (T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null ) map.set(this , value); else createMap(t, value); } void createMap (Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap (this , firstValue); }
get() 获取局部变量的 1 2 3 4 5 6 7 8 9 10 11 12 13 public T get () { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null ) { ThreadLocalMap.Entry e = map.getEntry(this ); if (e != null ) { T result = (T)e.value; return result; } } return setInitialValue(); }
如果调用 get 之前没 set 过,则 get 内部会执行 initialValue 方法进行初始化。
remove() 移除局部变量 1 2 3 4 5 public void remove () { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null ) m.remove(this ); }
为了防止内存泄露,使用该方法把 ThreadLocalMap 从当前线程移除。
ThreadLocalMap 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 static class ThreadLocalMap { static class Entry extends WeakReference <ThreadLocal<?>> { Object value; Entry(ThreadLocal<?> k, Object v) { super (k); value = v; } } private static final int INITIAL_CAPACITY = 16 ; private Entry[] table; private int size = 0 ; private int threshold; private void setThreshold (int len) { threshold = len * 2 / 3 ; } ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) { table = new Entry [INITIAL_CAPACITY]; int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1 ); table[i] = new Entry (firstKey, firstValue); size = 1 ; setThreshold(INITIAL_CAPACITY); } 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(); } }
FAQ
ThreadLocal 和 Synchronized 的区别? 同步机制保证的是共享变量;而 ThreadLocal 线程私有变量。
ThreadLocal 存储在 jvm 的哪个区域? ThreadLocal 对象也是对象,对象就在堆。
真的只是当前线程可见吗? 不是。可以通过 Thread#inheritableThreadLocals
变量实现,它也是 ThreadLocal.ThreadLocalMap 类型。
ThreadLocal 里的对象一定是线程安全的吗? 如果 ThreadLocal 保存的是共享变量,也会出现线程安全问题。
会导致内存泄漏么? Entry 对 key 是弱引用,不会导致内存泄漏,但是对 value 是强引用,可能导致内存泄漏。一般的线程终止以后,由于引用链断裂,ThreadLocalMap 中的数据也会被回收。 如果是线程池中的核心线程需要显示调用 remove() 方法移除局部变量。
其中虚线表示弱引用,从该图可以看出,一个 Thread 维持着一个 ThreadLocalMap 对象,而该 Map 对象的 key 又由提供该 value 的 ThreadLocal 对象弱引用提供,所以这就有这种情况:如果 ThreadLocal 不设为 static 的,由于 Thread 的生命周期不可预知,这就导致了当系统 gc 时将会回收它,而 ThreadLocal 对象被回收了,此时它对应 key 必定为 null,这就导致了该 key 对应得 value 拿不出来了,而 value 之前被 Thread 所引用,所以就存在 key 为 null、value 存在强引用导致这个 Entry 回收不了,从而导致内存泄露。 所以避免内存泄露的方法,是对于 ThreadLocal 要设为 static 静态的,除了这个,还必须在线程不使用它的值时手动 remove 掉该 ThreadLocal 的值,这样 Entry 就能够在系统 gc 的时候正常回收,而关于 ThreadLocalMap 的回收,会在当前 Thread 销毁之后进行回收。
总结 Thread 维护了一个 ThreadLocal.ThreadLocalMap 类型的变量,ThreadLocalMap 里维护了一个 Entry[] 数组,Entry 保存的是以 ThreadLocal 为 key,传入的值为 value 的键值对。