ThreadLocal与ThreadLocalMap源码分析 (2)

获取线程本地变量的方法。

public T get() { // 获取当前线程对象 Thread t = Thread.currentThread(); // 获取当前线程对象的容器map ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); // 如果容器不为null且容器内有当前threadlocal对象对应的值,则返回该值 if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } // 如果容器为null或者容器内没有当前threadlocal对象绑定的值,则先设置初始值并返回该初始值 return setInitialValue(); } // 设置初始值。主要分为两步:1.加载和获取初始值;2.在容器中设置该初始值。 // 第二步其实和set(value)方法实现一模一样。 private T setInitialValue() { // 加载并获取初始值,默认是null。如果是带参初始化的子类SuppliedThreadLocal,会有一个输入初始值。 // 当然也可以继承ThreadLocal类重写该方法设置初始值 T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); // 如果容器不为null,则直接设置元素。否则用线程对象t和value去初始化容器对象 if (map != null) map.set(this, value); else createMap(t, value); return value; }

移除线程本地变量的方法

public void remove() { // 如果容器不为null就调用容器的移除方法,移除和该threadlocal绑定的变量 ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); } ThreadLocal-hash值计算

ThreadLocal的hash值用于ThreadLocalMap容器计算数组下标。类中定义threadLocalHashCode表示其hash值。类中定义了静态方法和静态原子变量计算hash值,也就是说所有的threadLocal对象共用一个增长器。

// 当前ThreadLocal对象的hash值 private final int threadLocalHashCode = nextHashCode(); // 用来计算hash值的原子变量,所有的threadlocal对象共用一个增长器 private static AtomicInteger nextHashCode = new AtomicInteger(); // 魔法数字,使hash散列均匀 private static final int HASH_INCREMENT = 0x61c88647; // 计算hash值的静态方法 private static int nextHashCode() { return nextHashCode.getAndAdd(HASH_INCREMENT); }

我们使用同样的方法定义一个测试类,定义多个不同测试类对象,看看hash值的生成情况。如下所示,可以看到hash值都不同,是共用的一个增长器。

public class Test{ private static final int HASH_INCREMENT = 0x61c88647; public static AtomicInteger nextHashCode = new AtomicInteger(); public final int nextHashCode = nextHashCode(); private static int nextHashCode() { return nextHashCode.getAndAdd(HASH_INCREMENT); } public static void main(String[] args){ for (int i = 0; i < 5; i++) { Test test = new Test(); System.out.println(test.nextHashCode); } } // 输出的hash值 0 1640531527 -1013904242 626627285 -2027808484 } ThreadLocalMap类

ThreadLocalMap类是ThreadLocal的内部类。其作为一个容器,为ThreadLocal提供操作线程本地变量的功能。每一个Thread对象中都会有一个ThreadLocalMap对象实例(成员变量threadLocals,初始值为null)。因为map是Thread对象的非公共成员,不会被并发调用,所以不用考虑并发风险。

后文将从数据存储设计、初始化、增删数据等方面分析对应源码。

ThreadLocalMap-数据存储设计

该map和hashmap类似,使用一个Entry数组来存储节点元素,定义size变量表示当前容器中元素的数量,定义threshold变量用于计算扩容的阈值。

// Entry数组 private Entry[] table; // 容器内元素个数 private int size = 0; // 扩容计算用阈值 private int threshold;

不同的是Entry节点为WeakReference类的子类,使用引用字段作为键,将弱引用字段(通常是ThreadLocal对象)和值绑定在一起。使用弱引用是为了使得threadLocal对象可以被回收,(如果将key作为entry的一个成员变量,那线程销毁前,threadLocal对象不会被回收掉,即使该threadLocal对象不再使用)。

static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } } ThreadLocalMap-初始化

提供了带初始键和初始值的map构造方法,还有一个基于已有map的构造方法(用于ThreadLocal的子类InheritableThreadLocal初始化map容器,目的是将父线程的map传入子线程,会在创建子线程的过程中自动执行)。如下所示:

// 基于初始键值的构造函数 ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) { // 基于输入键值构建节点 table = new Entry[INITIAL_CAPACITY]; // 根据键的hash值计算所在数组下标 int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); // 采用懒加载的方式,只创建一个必要的节点 table[i] = new Entry(firstKey, firstValue); size = 1; // 设置阈值为初始长度的2/3,初始长度默认为12,那么阈值为为8 setThreshold(INITIAL_CAPACITY); } // 基于已有map的构造函数 private ThreadLocalMap(ThreadLocalMap parentMap) { // 获取传入map的节点数组 Entry[] parentTable = parentMap.table; int len = parentTable.length; setThreshold(len); // 构造相同长度的数组 table = new Entry[len]; // 深拷贝传入数组中各个节点到当前容器数组 // 注意这里因为采用开放地址解决hash冲突,拷贝后的元素在数组中的位置与原数组不一定相同 for (int j = 0; j < len; j++) { // 获取数组各个位置上的节点 Entry e = parentTable[j]; if (e != null) { @SuppressWarnings("unchecked") ThreadLocal<Object> key = (ThreadLocal<Object>) e.get(); if (key != null) { //确保key为InheritableThreadLocal类型,否则抛出UnsupportedOperationException Object value = key.childValue(e.value); Entry c = new Entry(key, value); // 根据hash值和数组长度,计算下标 int h = key.threadLocalHashCode & (len - 1); // 这里采用开放地址的方法解决hash冲突 // 当发生冲突时,就顺延到数组下一位,直到该位置没有元素 while (table[h] != null) h = nextIndex(h, len); table[h] = c; size++; } } } } ThreadLocalMap-移除元素

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zzpjdg.html