【Java】JDK源码分析——ThreadLocal

ThreadLocal源码分析

  • 一.概述
  • 二.源码分析
    • 1.全局变量
    • 2. nextHashCode方法
    • 3.构造方法
    • 4. initialValue方法
    • 5. getMap方法
    • 6. createMap方法
    • 7. createInheritedMap方法
    • 8. childValue方法
    • 9. remove方法
    • 10. set方法
    • 11. setInitialValue方法
    • 12. get方法
    • 13.ThreadLocal中的静态内部类ThreadLocalMap
      • 1)全局变量
      • 2)ThreadLocalMap中的静态内部类Entry
      • 3)构造方法
        • i)参数为键值对
        • ii)参数为ThreadLocalMap
      • 4)setThreshold方法
      • 5)nextIndex方法
      • 6)prevIndex方法
      • 7)expungeStaleEntry方法
      • 8)cleanSomeSlots方法
      • 9)expungeStaleEntries方法
      • 10)resize方法
      • 11)rehash方法
      • 12)replaceStaleEntry方法
      • 13)remove方法
      • 14)set方法
  • 三.总结

一.概述

ThreadLocal用于线程之间的变量共享,每一个线程都有自己独立的副本,用来存储自己的变量。只要线程是活跃的,其对应的副本就是可以访问的。当线程死亡,其副本会作为垃圾被回收。
ThreadLocal.java中的相关代码:

public class ThreadLocal<T> {}

1.泛型T为存储和获取数据的类型。

二.源码分析

1.全局变量

ThreadLocal.java中的相关代码:

    // ThreadLoacal的哈希值,用来区分不同线程的ThreadLocal
	private final int threadLocalHashCode = nextHashCode();

	// 用于计算哈希值,采用AtomicInteger保证并发时安全,初始值为0
	private static AtomicInteger nextHashCode = new AtomicInteger();

	// 用于计算哈希值
	private static final int HASH_INCREMENT = 0x61c88647;

2. nextHashCode方法

用于产生ThreadLocal的哈希值。
ThreadLocal.java中的相关代码:

	private static int nextHashCode() {
    	// 将nextHashCode的值加上HASH_INCREMENT的值,并返回
        return nextHashCode.getAndAdd(HASH_INCREMENT);
	}

3.构造方法

ThreadLocal.java中的相关代码:

    public ThreadLocal() {
	}

4. initialValue方法

用于为ThreadLocal提供当前线程的初始化值。
默认为空,如果有需要,可以重写该方法。
ThreadLocal.java中的相关代码:

    protected T initialValue() {
        return null;
	}

5. getMap方法

获取指定线程的ThreadLocalMap。
ThreadLocal.java中的相关代码:

	ThreadLocalMap getMap(Thread t) {
    	// 返回线程的全局变量
        return t.threadLocals;
	}

6. createMap方法

为指定线程创建ThreadLocalMap用于保存线程间共享的对象,并存储firstValue。
ThreadLocal.java中的相关代码:

	void createMap(Thread t, T firstValue) {
    	// 创建ThreadLocalMap对象
    	// 保存到线程的全局变量threadLocals中
        t.threadLocals = new ThreadLocalMap(this, firstValue);
	}

7. createInheritedMap方法

该方法用于根据父线程的ThreadLocalMap对象创建子线程继承的ThreadLocalMap对象。
ThreadLocal.java中的相关代码:

	static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
    	// 调用构造方法,创建新对象
        return new ThreadLocalMap(parentMap);
    }

8. childValue方法

该方法在InheritableThreadLocal类中被重写,用于根据父值而给出子值。
该方法为了在必要时继承的子类可以不用重写ThreadLocalMap类,而通过此方法实现原本的功能。
ThreadLocal.java中的相关代码:

    T childValue(T parentValue) {
        throw new UnsupportedOperationException();
	}

9. remove方法

删除当前线程在ThreadLocal中存储的值。
ThreadLocal.java中的相关代码:

	public void remove() {
         // 获取当前线程的ThreadLocalMap对象
         ThreadLocalMap m = getMap(Thread.currentThread());
         // 若ThreadLocalMap不为空
         if (m != null)
             m.remove(this); // 移除当前线程存储的值
     }

10. set方法

保存值到当前线程的ThreadLocalMap中。
ThreadLocal.java中的相关代码:

	public void set(T value) {
    	// 获取当前线程
        Thread t = Thread.currentThread();
        // 获取当前线程的ThreadLocalMap对象
        ThreadLocalMap map = getMap(t);
        // 若ThreadLocalMap对象存在
        if (map != null)
            // 直接保存value
            map.set(this, value);
        else // 若ThreadLocalMap对象不存在
            // 则创建ThreadLocalMap对象,并保存value
            createMap(t, value);
    }

11. setInitialValue方法

用于将当前线程的ThreadLocalMap存储的值设置为初始值。
ThreadLocal.java中的相关代码:

	private T setInitialValue() {
    	// 获取初始值
        T value = initialValue();
        // 获取当前线程
        Thread t = Thread.currentThread();
        // 获取当前线程的ThreadLocalMap对象
        ThreadLocalMap map = getMap(t);
		// 若ThreadLocalMap对象存在
        if (map != null)
            map.set(this, value); // 则保存值value
        else // 若ThreadLocalMap对象不存在
            createMap(t, value); // 则创建ThreadLocalMap对象,再保存值value
        // 返回初始值
        return value;
	}

12. get方法

获取当前线程的ThreadLocalMap中对应的值。
ThreadLocal.java中的相关代码:

	public T get() {
    	// 获取当前线程
        Thread t = Thread.currentThread();
        // 获取当前线程的ThreadLocalMap对象
        ThreadLocalMap map = getMap(t);
        // 若ThreadLocalMap对象存在
        if (map != null) {
            // 尝试获取当前ThreadLocal对应的键值对
            ThreadLocalMap.Entry e = map.getEntry(this);
            // 若键值对存在,说明之前存储过
            if (e != null) {
                @SuppressWarnings("unchecked")
                // 获取键值对的值
                T result = (T)e.value;
                // 返回
                return result;
            }
        }
        // 若ThreadLocalMap对象不存在,
		// 则将ThreadLocal对应的值设置为初始值,并返回
        return setInitialValue();
}

13.ThreadLocal中的静态内部类ThreadLocalMap

ThreadLocalMap是ThreadLocal用于存储键值对的内部类,ThreadLocal中对数据操作都是通过该内部类提供的方法实现的。
ThreadLocal.java中的相关代码:

static class ThreadLocalMap {}

1)全局变量

ThreadLocal.java中的相关代码:

		// 初始化容量,表示能够存储键值对的数量,必须是2的次方形式
        private static final int INITIAL_CAPACITY = 16;

        // 用于存储键值对的数组
        private Entry[] table;

        // 用于记录已经存储的键值对的数量
        private int size = 0;

        // 扩容的阈值,存储的键值对数量超过该值将会触发扩容,默认为0
        private int threshold;

2)ThreadLocalMap中的静态内部类Entry

该类用于存储键值对,其中键的类型为ThreadLocal,值的类型为Object。
同时,该类继承了弱引用类WeakReference,当获取键为空时,说明该键的引用被回收。
ThreadLocal.java中的相关代码:

		static class Entry extends WeakReference<ThreadLocal<?>> {
            // 线程中存储的值
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

3)构造方法

i)参数为键值对

该方法会创建ThreadLocalMap,并将参数中的键值对进行保存。
ThreadLocal.java中的相关代码:

		ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            // 根据初始化容量,创建键值对数组
            table = new Entry[INITIAL_CAPACITY];
            // 获取ThreadLocal的哈希值,和INITIAL_CAPACITY – 1进行与运算
            // 计算键值对在数组中的位置
            // INITIAL_CAPACITY – 1作为掩码
			// 如16-1=15,1111,最后进行与运算得到的数值一定在0到15之间
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            // 创建新的键值对,保存到数组对应的位置中
            table[i] = new Entry(firstKey, firstValue);
            // 记录当前键值对数量1
            size = 1;
            // 设置扩容阈值
            setThreshold(INITIAL_CAPACITY);
        }

ii)参数为ThreadLocalMap

该方法用来根据继承的ThreadLocalMap的内容创建新的ThreadLocalMap。
该方法是私有的,仅供createInheritedMap方法调用。
ThreadLocal.java中的相关代码:

		private ThreadLocalMap(ThreadLocalMap parentMap) {
            // 获取继承的ThreadLocalMap中的键值对数组
            Entry[] parentTable = parentMap.table;
            // 获取数组长度
            int len = parentTable.length;
            // 根据长度设置扩容阈值
            setThreshold(len);
            // 根据长度创建新数组
            table = new Entry[len];

            // 循环,将键值对转移到新数组中
            for (int j = 0; j < len; j++) {
                // 获取键值对
                Entry e = parentTable[j];
                // 若j位置键值对存在
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    // 获取键
                    ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
                    // 若键存在
                    if (key != null) {
                        // 根据键,获取值
                        Object value = key.childValue(e.value);
                        // 根据键和值,创建新的键值对
                        Entry c = new Entry(key, value);
                        // 根据键ThreadLocal的哈希值
						// 计算出键值对在数组中的位置h
						// len - 1作为掩码
						// 如16-1=15,1111,
						// 最后进行与运算得到的数值一定在0到15之间
                        int h = key.threadLocalHashCode & (len - 1);
                        // 循环,若数组的h位置有键值对
                        // 则根据当前位置h和数组长度,获取下一个位置
                        // 直到找到一个没有键值对的位置
                        while (table[h] != null)
                            h = nextIndex(h, len);
                        // 将键值对存在对应位置
                        table[h] = c;
                        // 当前存储的键值对数量加1
                        size++;
                    }
                }
            }
        }

4)setThreshold方法

用来设置扩容阈值。
ThreadLocal.java中的相关代码:

		private void setThreshold(int len) {
            // 阈值为数组长度的三分之二
            threshold = len * 2 / 3;
        }

5)nextIndex方法

获取位置i的下一个位置。
ThreadLocal.java中的相关代码:

		private static int nextIndex(int i, int len) {
            // 若i的下一个位置i+1小于数组长度len
            // 则返回i+1,否则返回0
            // 该操作相当于i对len取模
            return ((i + 1 < len) ? i + 1 : 0);
        }

6)prevIndex方法

获取位置i的上一个位置。
ThreadLocal.java中的相关代码:

		private static int prevIndex(int i, int len) {
            // 若i的上一个位置i-1大于0
            // 则返回i-1,否则返回len-1
            return ((i - 1 >= 0) ? i - 1 : len - 1);
        }

7)expungeStaleEntry方法

删除指定位置的键值对,并对其它位置的键值对重新散列整理。
ThreadLocal.java中的相关代码:

		private int expungeStaleEntry(int staleSlot) {
            // 获取数组
            Entry[] tab = table;
            // 获取数组的长度
            int len = tab.length;

            // 删除指定位置的键值对
            tab[staleSlot].value = null;
            tab[staleSlot] = null;
            // 存储的键值对数量减1
            size--;

            // 对数组循环整理,重新散列,
			// 直到遇到为空的位置,停止循环
            Entry e;
            int i;
            // 从staleSlot下一个位置i开始
            // 若i位置的键值对e为空,则跳出循环
            // 每次循环i向后移动一个位置,若移动到最后一个位置,则回到0开始
            for (i = nextIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = nextIndex(i, len)) {
                // 从i位置的键值对e中获取键k
                ThreadLocal<?> k = e.get();
                // 若键为空
                if (k == null) {
                    // 清除当前位置的键值对
                    e.value = null;
                    tab[i] = null;
                    // 存储的键值对数量减1
                    size--;
                } else { // 若键不为空
                    // 重新计算键值对应该存放的位置h
                    int h = k.threadLocalHashCode & (len - 1);
                    // 若键值对应该存储的位置和当前存放的位置不一致
                    if (h != i) {
                        // 对当前的位置进行清空
                        tab[i] = null;

                        // 循环,从键值对应该存放的位置h开始找
                        // 找到一个空位置,存放键值对
                        while (tab[h] != null)
                            h = nextIndex(h, len);
                        tab[h] = e;
                    }
                }
            }
            // 返回跳出循环时键值对为空的位置
            return i;
        }

8)cleanSomeSlots方法

从位置i的下一位开始,向下查看log2(n)个位置,如果发现位置不对的键值对,则对其进行清理。
ThreadLocal.java中的相关代码:

		private boolean cleanSomeSlots(int i, int n) {
            // 标志位,如果进行过清理,则为true
            boolean removed = false;
            // 获取键值对数组
            Entry[] tab = table;
            // 获取数组长度
            int len = tab.length;
            // 循环
            do {
                // 获取位置i的下一个位置,结果再赋给i
                i = nextIndex(i, len);
                // 获取该位置的键值对
                Entry e = tab[i];
                // 若该位置的键值对存在,并且键值对的键不存在
                if (e != null && e.get() == null) {
                    // 重新设置循环次数
                    n = len;
                    // 设置标志位
                    removed = true;
                    // 删除指定位置的键值对,并进行整理
                    i = expungeStaleEntry(i);
                }
            } while ( (n >>>= 1) != 0); // 循环log2(n)次
            // 返回结果
            return removed;
        }

9)expungeStaleEntries方法

删除数组中键不存在的键值对,对位置错误的键值对进行调整。
ThreadLocal.java中的相关代码:

		private void expungeStaleEntries() {
            // 获取键值对数组
            Entry[] tab = table;
            // 获取数组长度
            int len = tab.length;
            // 循环遍历键值对数组
            for (int j = 0; j < len; j++) {
                // 获取键值对
                Entry e = tab[j];
                // 若键值对存在,并且键值对的键不存在
                if (e != null && e.get() == null)
                    // 删除该位置的键值对,并整理。
                    expungeStaleEntry(j);
            }
        }

10)resize方法

用于对键值对数组扩容,扩大之前容量的2倍。
ThreadLocal.java中的相关代码:

		private void resize() {
            // 获取旧的键值对数组
            Entry[] oldTab = table;
            // 获取旧的键值对数组的长度
            int oldLen = oldTab.length;
            // 根据旧长度计算新长度,新长度为旧长度的2倍
            int newLen = oldLen * 2;
            // 根据新长度创建新数组
            Entry[] newTab = new Entry[newLen];
            // 用于记录新数组使用的空间数量
            int count = 0;

            // 循环遍历旧数组
            for (int j = 0; j < oldLen; ++j) {
                // 取出键值对
                Entry e = oldTab[j];
                // 若键值对存在
                if (e != null) {
                    // 获取键值对的键
                    ThreadLocal<?> k = e.get();
                    // 若键不存在
                    if (k == null) {
                        // 则将其值删除,即释放引用
                        e.value = null; // 可以帮助垃圾回收器快点回收
                    } else { // 若键存在
                        // 重新计算其在新数组中的位置
                        int h = k.threadLocalHashCode & (newLen - 1);
                        // 若该位置已经被占了,则循环查找下一个可以存放的位置
                        while (newTab[h] != null)
                            h = nextIndex(h, newLen);
                        // 存放键值对到新数组中
                        newTab[h] = e;
                        // 新数组已经使用的空间数量加1
                        count++;
                    }
                }
            }

            // 设置新的扩容阈值
            setThreshold(newLen);
            // 将局部变量保存到全局变量
            // 将已经使用的空间数量保存到全局变量
            size = count;
            // 将新数组保存到全局变量
            table = newTab;
        }

11)rehash方法

对所有的键值对进行整理,删除键不存在的键值对。
如果整理后键值对的数量还是过高,则进行扩容。
ThreadLocal.java中的相关代码:

 		private void rehash() {
            // 整理所有键值对。
            expungeStaleEntries();

            // 当数组已经使用的空间数量超过阈值的四分之三
			// 即总空间的六分之一,则进行扩容
			// 使用更低的阈值避免滞后现象
            if (size >= threshold - threshold / 4)
                resize();
        }

12)replaceStaleEntry方法

该方法用于在set方法中将staleSlot位置处的键值对替换成键为key值为value的键值对。
ThreadLocal.java中的相关代码:

		private void replaceStaleEntry(ThreadLocal<?> key, Object value, int staleSlot) {

            // 获取键值对数组
            Entry[] tab = table;
            // 获取键值对数组的长度
            int len = tab.length;
            Entry e;

            // slotToExpunge用来标记需要进行删除的位置
            int slotToExpunge = staleSlot;
            // 循环向前查找,找到一个键值对存在,
			// 但键值对中键不存在(被回收)的位置,直到遇到空的键值对结束循环
            for (int i = prevIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = prevIndex(i, len))
                if (e.get() == null)
                    slotToExpunge = i; // 用slotToExpunge标记该位置

            // 循环向后查找键值对,直到遇到空的键值对结束循环
            for (int i = nextIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = nextIndex(i, len)) {
                ThreadLocal<?> k = e.get();

                // 若键相等
                // 即本来要插入在staleSlot位置,但现在在i位置找到了对应的键
                if (k == key) {
                    // 修改键的值为新值
                    e.value = value;

                    // 交换i位置和staleSlot位置的键值对
                    tab[i] = tab[staleSlot];
                    tab[staleSlot] = e;

                    // 若二者相等,说明之前循环向前查找没有找到要找的键值对
                    if (slotToExpunge == staleSlot)
                        // 标记需要删除的位置i,
						// i位置的键值对已经更新并转移到staleSlot位置
                        slotToExpunge = i; 
                    // 删除i位置的键值对,并对键值对数组进行整理 
                    cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
                    // 返回
                    return;
                }

                // 若k为空同时slotToExpunge和staleSlot相等
                // k为空说明,当前的i位置是满足键值对存在但键不存在的位置
                // slotToExpunge和staleSlot相等说明该位置之前向前查找时没有找到
                if (k == null && slotToExpunge == staleSlot)
                    // 标记需要删除的位置i
                    slotToExpunge = i;
            }

            // 代码执行到此,说明循环中没有找到存在的键值对,
			// 该键值对之前没有存储过
            // 删除staleSlot位置的值(释放引用,加速垃圾回收)
            tab[staleSlot].value = null;
            // 创建新键值对并存储
            tab[staleSlot] = new Entry(key, value);

            // 若slotToExpunge和staleSlot不相等
            // 说明在循环向后查找中找到了需要删除的键值对
            if (slotToExpunge != staleSlot)
                // 删除slotToExpunge位置的键值对,并进行整理
                cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
        }

13)remove方法

移除键为key的键值对。
ThreadLocal.java中的相关代码:

		private void remove(ThreadLocal<?> key) {
            // 获取键值对数组
            Entry[] tab = table;
            // 获取键值对数组长度
            int len = tab.length;
            // 计算键值对应该存储的位置
            int i = key.threadLocalHashCode & (len-1);
            // 从i位置循环向后查找,直到遇到空键值对结束循环
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                // 若i位置的键值对的将和要移除的键值对的键相等
                if (e.get() == key) {
                    // 释放键值对的引用(加速垃圾回收)
                    e.clear();
                    // 删除位置i的键值对e
                    expungeStaleEntry(i);
                    // 返回
                    return;
                }
            }
        }

14)set方法

添加键值对。
ThreadLocal.java中的相关代码:

		private void set(ThreadLocal<?> key, Object value) {

            // 获取键值对数组
            Entry[] tab = table;
            // 获取键值对数组长度
            int len = tab.length;
            // 计算键值对应该存储的位置i
            int i = key.threadLocalHashCode & (len-1);

            // 循环从位置i向后查找,直到遇到空键值对跳出循环
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                // 获取键值对的键
                ThreadLocal<?> k = e.get();

                // 若i位置键值对的键k和要添加的键值对的键key相等
                // 说明之前添加过
                if (k == key) {
                    // 更新键值对的值为新的值value
                    e.value = value;
                    // 返回
                    return;
                }

                // 若当前位置i的键值对的键不存在(被回收)
                if (k == null) {
                    // 将新的键值对添加到位置i处
                    replaceStaleEntry(key, value, i);
                    // 返回
                    return;
                }
            }

            // 代码执行到此处,说明该键值对之间没有添加过
            // 并且也没有合适的位置
            // 创建新的键值对,保存到位置i
            tab[i] = new Entry(key, value);
            // 当前存储的键值对数量加1,并将其值赋给sz
            int sz = ++size;
            // 若整理空间失败,同时当前存储的键值对数量超过了扩容阈值
            // 说明当前存储空间不足
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                // 重新散列,删除键不存在的键值对,必要时将进行扩容
                rehash();
        }

三.总结

      1.ThreadLocal是用于在线程中存储数据,其内部是通过ThreadLocalMap来存储的,每一个线程都有一个对应ThreadLocalMap,ThreadLocalMap的内部维护了一个Entry数组。
      2.一个Entry就是一个键值对,该键值对键的类型为ThreadLocal,值的类型为Object。其中,由于Entry继承了WeakReference,因此每个键值对Entry对键ThreadLocal的引用都是弱引用。
      3.每个ThreadLocal对象被创建时,都有不同的哈希值,该值会通过计算得到以该ThreadLocal为键的键值对的存储位置。当我们调用ThreadLocal的get和set方法,会将以该ThreadLocal为键的键值对存储到当前线程的ThreadLocalMap中。
      4.ThreadLocalMap存储在线程中,但是对ThreadLocalMap的操作(如:创建、整理、删除、添加、获取…)须要通过ThreadLocal对象的方法来实现。
      5.ThreadLocalMap内部提供方法,会对键被回收的键值对进行清理,以保证空间存储需要的键值对,当清理后的空间还是不足时,会触发扩容。ThreadLocalMap的初始容量为16,每次扩容会会扩大之前的2倍。

你可能感兴趣的:(Java)