threadLocal源码分析

2021/12/9 17:18:43

本文主要是介绍threadLocal源码分析,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

threadLocal源码分析

  • threadLocal.set()方法
  • threadLocalMap构造
  • 如何把threadLocal保存到map
  • threadLocalMap如何解决hash冲突
  • threadLocalMap如何重新计算元素的位置

threadLocal.set()方法

	public void set(T value) {
        // 获取当前线程
        Thread t = Thread.currentThread();
        // 获取当前线程的ThreadLocalMap
        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);
    }

通过此方法我们可以看到ThreadLocalMap是被每个线程独立拥有的,每个线程持有一个map的引用,这也是ThreadLocal实现线程隔离的思想

这样就把要保存数据的生命周期交给了thread自己管理,thread销毁的同时,保存的数据也一起跟着被销毁

threadLocalMap构造

	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;
     }

可以看出这并不是我们平常所看到的map,是通过table数组保存entry对象,同时每个entry对象保存相应的key-value

如何把threadLocal保存到map

思考以下问题:

  1. 怎么计算数组下标?
  2. 两个元素插入时候数组下标相同时,如何处理这种冲突?

带着问题的开始寻找答案

threadLocalMap如何解决hash冲突

这里使用了线性探测法解决hash冲突

看threadLocalMap的set方法:

	private void set(ThreadLocal<?> key, Object value) {
            Entry[] tab = table;
            int len = tab.length;
            // 通过位运算计算数组下标,位运算保证i的值小于等于len-1
            int i = key.threadLocalHashCode & (len-1);

            /**
             * 插入的时候发生了hash冲突
             */
            for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();
                /**
                 * 说明key已经存在了,直接覆盖就行了
                 */
                if (k == key) {
                    e.value = value;
                    return;
                }

                /**
                 * 数组元素不等于null,但是key等于null,说明ThreadLocal弱引用已经被回收
                 * 但是value依然存在,需要释放该元素同时插入新的元素
                 */
                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
                // 如果上面两个条件都不满足
                // 继续寻找下一个null的槽位,i的值一直在变化
            }

            /**
             * 没有产生hash冲突的话,就直接在该数组位置插入
             */
            tab[i] = new Entry(key, value);
            int sz = ++size;
            // 双重判断
            // 1 首先判断是否有可以被回收的节点
            // 2 判断size是否超过了临界值
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

threadLocalMap如何重新计算元素的位置

我们先看map删除一个元素都做了什么操作,然后一步步分析

	private int expungeStaleEntry(int staleSlot) {
            Entry[] tab = table;
            int len = tab.length;

            //1 expunge entry at staleSlot
            tab[staleSlot].value = null;
            tab[staleSlot] = null;
            size--;

            // Rehash until we encounter null
            Entry e;
            int i;
            //2 开始整理工作
            for (i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) {
                ThreadLocal<?> k = e.get();
                /** 3
                 * 开始清理工作,说明这个threadlocal已经被gc回收了
                 * 但是value作为强引用依然没有被回收,这里需要进行一个清理工作
                 */
                if (k == null) {
                    e.value = null;
                    tab[i] = null;
                    size--;
                } else {
                    int h = k.threadLocalHashCode & (len - 1);
                    //4 把e放到它正真应该存在的位置去
                    if (h != i) {
                        // 释放当前槽位
                        tab[i] = null;

                        // Unlike Knuth 6.4 Algorithm R, we must scan until
                        // null because multiple entries could have been stale.
                        // 发现本属于自己位置还是依然被占用着
                        // 继续寻找插入e的位置(用开放寻址法来解决散列冲突)
                        // 5
                        while (tab[h] != null)
                            h = nextIndex(h, len);
                        // 6
                        tab[h] = e;
                    }
                }
            }
            return i;
        }
  1. 把相应的元素位置置为null,删除该元素
  2. 遍历所有不为null的元素
  3. 如果发现元素的key==null说明threadLocal引用已经被回收,需要把该元素置为null,等待下次垃圾回收
  4. 如果key不等于null,查看元素所处的位置和计算的hash是否是一致的,如果不一致,重新查找它的正确位置
  5. 如果正确的位置已经被占用了,那就继续查找空闲的位置
  6. 找到空闲位置之后赋值


这篇关于threadLocal源码分析的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程