概念
ThreadLocal 用于提供线程局部变量,在多线程环境可以保证各个线程里的变量独立于其它线程里的变量。也就是说 ThreadLocal 可以为每个线程创建一个【单独的变量副本】,相当于线程的 private static 类型变量。
使用示例
1public class ThreadLocalTest { 2 private static String strLabel; 3 private static ThreadLocal<String> threadLabel = new ThreadLocal<>(); 4 5 public static void main(String... args) { 6 strLabel = "main"; 7 threadLabel.set("main"); 8 9 Thread thread = new Thread() { 10 11 @Override 12 public void run() { 13 super.run(); 14 strLabel = "child"; 15 threadLabel.set("child"); 16 } 17 18 }; 19 20 thread.start(); 21 try { 22 // 保证线程执行完毕 23 thread.join(); 24 } catch (InterruptedException e) { 25 e.printStackTrace(); 26 } 27 28 System.out.println("strLabel = " + strLabel); 29 System.out.println("threadLabel = " + threadLabel.get()); 30 } 31} 32 33// 运行结果 34// strLabel = child 35// threadLabel = main
ThreadLocal 实现
set方法
1public void set(T value) { 2 Thread t = Thread.currentThread(); 3 ThreadLocalMap map = getMap(t); 4 if (map != null) 5 map.set(this, value); 6 else 7 createMap(t, value); 8} 9
set(T value) 方法中,首先获取当前线程,然后在获取到当前线程的 ThreadLocalMap,如果 ThreadLocalMap 不为 null,则将 value 保存到 ThreadLocalMap 中,并用当前 ThreadLocal 作为 key;否则创建一个 ThreadLocalMap 并给到当前线程,然后保存 value。 ThreadLocalMap 相当于一个 HashMap,是真正保存值的地方。
get方法
1public T get() { 2 Thread t = Thread.currentThread(); 3 ThreadLocalMap map = getMap(t); 4 if (map != null) { 5 ThreadLocalMap.Entry e = map.getEntry(this); 6 if (e != null) { 7 @SuppressWarnings("unchecked") 8 T result = (T)e.value; 9 return result; 10 } 11 } 12 return setInitialValue(); 13} 14
同样的,在 get() 方法中也会获取到当前线程的 ThreadLocalMap,如果 ThreadLocalMap 不为 null,则把获取 key 为当前 ThreadLocal 的值;否则调用 setInitialValue() 方法返回初始值,并保存到新创建的 ThreadLocalMap 中。
当前线程的 ThreadLocalMap
在 set,get,initialValue 和 remove 方法中都会获取到当前线程,然后通过当前线程获取到 ThreadLocalMap,如果 ThreadLocalMap 为 null,则会创建一个 ThreadLocalMap,并给到当前线程。
构造方法
1ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) { 2 table = new Entry[INITIAL_CAPACITY]; 3 int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); 4 table[i] = new Entry(firstKey, firstValue); 5 size = 1; 6 setThreshold(INITIAL_CAPACITY); 7}
构造方法中会新建一个数组,并将将第一次需要保存的键值存储到一个数组中,完成一些初始化工作。
存储结构
ThreadLocalMap 内部维护了一个哈希表(数组)来存储数据,并且定义了加载因子:
1// 初始容量,必须是 2 的幂 2private static final int INITIAL_CAPACITY = 16; 3 4// 存储数据的哈希表 5private Entry[] table; 6 7// table 中已存储的条目数 8private int size = 0; 9 10// 表示一个阈值,当 table 中存储的对象达到该值时就会扩容 11private int threshold; 12 13// 设置 threshold 的值 14private void setThreshold(int len) { 15 threshold = len * 2 / 3; 16}
假设 Entry 的 key 没有使用弱引用的方式,而是使用了强引用:由于 ThreadLocalMap 的生命周期和当前线程一样长,那么当引用 ThreadLocal 的对象被回收后,由于 ThreadLocalMap 还持有 ThreadLocal 和对应 value 的强引用,ThreadLocal 和对应的 value 是不会被回收的,这就导致了内存泄漏。所以 Entry 以弱引用的方式避免了 ThreadLocal 没有被回收而导致的内存泄漏,但是此时 value 仍然是无法回收的,依然会导致内存泄漏。
ThreadLocalMap 已经考虑到这种情况,并且有一些防护措施:在调用 ThreadLocal 的 get(),set() 和 remove() 的时候都会清除当前线程 ThreadLocalMap 中所有 key 为 null 的 value。这样可以降低内存泄漏发生的概率。所以我们在使用 ThreadLocal 的时候,每次用完 ThreadLocal 都调用 remove() 方法,清除数据,防止内存泄漏。
文章有帮助你,请关注微信公众号:肆意游离 有更多精彩等着你