ThreadLocal源码解析2.ThreadLocalMap

1.ThreadLocalMap内部结构

   static class ThreadLocalMap {
    
    
		
     	
     	/*
     	 *  这里的Entry继承了弱引用,(弱引用只要发生GC就要被回收)
     	 *  Entry的key(ThreadLocal)以弱引用的方式指向ThreadLocal对象。
     	 *  (可以避免内存泄露)
     	 *  (参考 https://blog.csdn.net/qq_46312987/article/details/117166100)
     	 */
        static class Entry extends WeakReference<ThreadLocal<?>> {
    
    
            Object value;			
            Entry(ThreadLocal<?> k, Object v) {
    
    
                super(k);
                value = v;
            }
        }
		
     	//Entry数组初始长度,数组的长度必然是2的次方数
        private static final int INITIAL_CAPACITY = 16;
		
     	//Entry数组
        private Entry[] table;
		
     	//Entry数组中的元素个数
        private int size = 0;
		
     
     	/*
     	 * 扩容阈值 初始值为 len * 2/3
     	 * 触发后调用 rehash()方法
     	 * rehash() 方法先做一次全局检查过期数据,把散列表中所有过期的entry移除
     	 * 如果移除之后 当前散列表中的entry个数仍然达到 阈值的3/4,就进行扩容。
     	 */
        private int threshold; 
     
     	//设置阈值为当前数组长度的2/3
     	private void setThreshold(int len) {
    
    
            threshold = len * 2 / 3;
        }
			
        
     	/*
     	 * @param i 当前下标
     	 * @param len 数组长度
  		 * 返回当前位置的下一个位置
     	 */
        private static int nextIndex(int i, int len) {
    
    
            /*
             * 当前下标+1 如果小于数组长度的话 返回 +1的值
             * 如果等于数组长度的话,就返回0,即形成了环
             */
            return ((i + 1 < len) ? i + 1 : 0);
        }
		
        /*
         * 跟nextIndex()方法类似。返回当前位置的上一个位置
         */
        private static int prevIndex(int i, int len) {
    
    
            return ((i - 1 >= 0) ? i - 1 : len - 1);
        }
      	
     
     	/*
     	 * 构造方法
     	 */
        ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
    
    
           //构造一个初始长度为16的Entry数组
           table = new Entry[INITIAL_CAPACITY];
           //寻址,(hash值 & length - 1)
           int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
           //构造一个Entry对象放到指定的位置上
           table[i] = new Entry(firstKey, firstValue);
           //size = 1  
           size = 1;
           //设置阈值为初始化容量(16)的2 / 3 = 10
           setThreshold(INITIAL_CAPACITY);
       }
 }

2.getEntry()方法详解

//----------------------------ThreadLocal.get()-------------------------------
// 此方法详解在 [https://blog.csdn.net/qq_46312987/article/details/121799343]
// 这里我们详细讲解 getEntry()方法。
	public T get() {
    
    
        //获取当前线程对象
        Thread t = Thread.currentThread();
        //获取当前线程内部的threadLcoalMap
        ThreadLocalMap map = getMap(t);
        //内部的map不为NULL,
        if (map != null) {
    
    
            //这里调用了getEntry(),去threadLocalMap中查询指定的数据
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
    
    
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }
//-----------------------------ThreadLocalMap.getEntry()---------------------
		/*
    	 *  ThreadLocal对象的get()操作实际上是由ThreadLocalMap.getEntry()完成的。
    	 */   
		private Entry getEntry(ThreadLocal<?> key) {
    
    
            //寻址
            int i = key.threadLocalHashCode & (table.length - 1);
            //获取指定位置的Entry
            Entry e = table[i];
            /*
             * 当前位置不为NULL 并且当前位置的key(ThreadLocal对象)与传来的key是否一致
             * 注意 ThreadLocalMap与HashMap或者是ConcurrentHashMap最大的区别就是
             * 当出现hash冲突时使用的是开放寻址法(找下一个位置)而不是拉链法(在当前位置形成			 * 链表)
             */
            if (e != null && e.get() == key)
                return e;
            else
                //在当前位置没找到就去后面继续找。
                return getEntryAfterMiss(key, i, e);
        }

//-----------------------ThreadLocalMap.getEntryAfterMiss()-------------------
		/*
		 * @param key   ThreadLocal对象
		 * @param i     第一次寻址的索引位置
		 * @param e     table第i个位置的entry对象
		 * 
		 * 此方法的作用就是从当前位置向后查询,查询到指定数据返回,当查询到某一个位置的			 * Entry为NULL时结束,最终返回NULL,在查询过程中如果某一个位置的entry不为NULL,
		 * 但是key为NULL,说明对应的当前entry关联的ThreadLcoal对象已经被回收了,那么就		 * 会将当前的entry清理掉
		 */		
        private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
    
    
            Entry[] tab = table;
            int len = tab.length;
			//向后查找如果还没有查找到 但是当前位置的entry为NUll,说明查找不到,结束循环
            while (e != null) {
    
    
                
                //entry继承了弱引用,get()方法就是获取内部的ThreadLocal对象
                ThreadLocal<?> k = e.get();
                
                //查找成功,返回entry。
                if (k == key)
                    return e;
                
                /* key是NULL,说明entry关联的ThreadLocal被回收了,但是entry还存在,
                 * 这时就需要将当前位置的entry干掉。
                 */
                if (k == null)
                    expungeStaleEntry(i);
                //k不为NULL,但是当前entry不是目标entry,继续向后查找
                else
                    i = nextIndex(i, len);
                e = tab[i];
            }
            return null;
        }

//--------------------------ThreadLocalMap.expungeStaleEntry()----------------
		
		/*
		 * @param staleSlot (stale翻译为过期的) 此方法的作用
		 * 1.即当前位置上的entry的key是NULL,说明当前的entry已经没有用了。
		 * 需要将其干掉。
		 * 2.遍历哈希表,将从当前位置开始的entry != null && key == null的所有entry		 *	 干掉,然后将正常的entry做一次重新迁移,优化查询。           
		 */
        private int expungeStaleEntry(int staleSlot) {
    
    
            Entry[] tab = table;
            int len = tab.length;
			
            //将当前位置的entry的的value置为NULL(help GC)
            tab[staleSlot].value = null;
            //将当前位置置为NULL,将entry直接干掉
            tab[staleSlot] = null;
            //size - 1
            size--;
			
            //下面就是rehash()的过程
            
            //当前遍历的entry
            Entry e; 
            
            //当前的位置
            int i;
            
 //循环遍历数组(从置为NULL的entry对象的下一个位置开始),直到某一位置上的entry为NULL为止。
            for (i = nextIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = nextIndex(i, len)) {
    
    
               	
                /*
                 * 能进入for循环,说明当前位置的entry一定不为NULL。
                 */
                ThreadLocal<?> k = e.get();
                
              //k(ThreadLocal对象) = NULL,将当前entry的value和当前entry全部干掉。
                if (k == null) {
    
    
                    e.value = null;
                    tab[i] = null;
                    size --;
                } else {
    
    
                    
                    /*
                     *  执行到这里,说明当前位置的entry不为NULL。
                     *  此时需要执行的事就是判断当前位置上的entry是否在经过哈希寻址后应					   *  该在的位置,(因为有可能发生过冲突),如果不在该在的位置,就去寻找
                     *  距离寻址位置最近的位置(也可能找到寻址的位置)。
                     */
                    
                    //重新计算索引
                    int h = k.threadLocalHashCode & (len - 1);
                    
                    //条件成立 表示当前entry确实不在该在的位置,需要尝试重新找位置存放。
                    if (h != i) {
    
    
                        tab[i] = null;
                        //循环找位置存放
                        while (tab[h] != null)
                            h = nextIndex(h, len);
                        tab[h] = e;
                    }
                }
            }
            return i;
        }

3.getEntry()运行流程图

在这里插入图片描述

4.set()方法详解

//-----------------ThreadLocalMap.set()---------------------------------------
	private void set(ThreadLocal<?> key, Object value) {
    
    
            
            //寻址
            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);
			
            /*
             *  for循环做的事就是,循环寻找key相同的entry。
             *  1.找到相同key并且正常的entry,做value替换
             *  2.找到某一位置(entry != null && entry.key == NULL),将entry替换。
             */
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
    
    
                
                ThreadLocal<?> k = e.get();
				
                //找到了相同的key,替换value。
                if (k == key) {
    
    
                    e.value = value;
                    return;
                }
				
                //查找过程中,碰到了entry.key == null,说明当前entry是过期数据
                if (k == null) {
    
    
                    //替换过期的entry。
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }
			
            /*
             * 执行到这里,说明for循环找到了一个当前slot为NULL的情况,
             * 此时直接在这个slot位置上创建一个Entry对象。
             */
            tab[i] = new Entry(key, value);
            int sz = ++size;
            
            /*
             * 这里做一次清理工作,cleanSomeSlots()返回true表示内部没有清理到数据
             * 这时在判断元素数量是否达到了扩容阈值,大于等于阈值就进行rehash()操作
             */
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

//----------------------ThreadLocalMap.replaceStaleEntry----------------------
		/*
		 * 替换过期的entry。
		 *  @param key 新key
		 *  @param value 新value
		 *  @param staleSlot 过期entry的位置
		 */
 private void replaceStaleEntryreplaceStaleEntry
(ThreadLocal<?> key, Object value,int staleSlot) {
    
    
     
            Entry[] tab = table;
            int len = tab.length;
            Entry e;
			
            //将过期entry的位置赋值给slotToExpunge
            int slotToExpunge = staleSlot;
            
            /*
             * 以当前staleSlot位置的前一个位置开始,向前迭代查找,
             * (结束条件entry = null),更新slotToExpunge为靠前的
             * (entry != null && entry.key == null) 的过期entry的位置。
             */
            for (int i = prevIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = prevIndex(i, len)){
    
    
                if (e.get() == null){
    
    
                    slotToExpunge = i;
             }
          }
			
            /*
             * 从当前不合法的entry的位置的下一个位置开始遍历,
             * 
             */
            for (int i = nextIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = nextIndex(i, len)) {
    
    
                
                //获取当前位置的entry的key(threadLocal)
                ThreadLocal<?> k = e.get();
			    
                /*
                 * key要添加的新key
                 * k 当前遍历的key
                 * k == key 说明要添加的key已经存在了,需要替换value
                 * 然后做清理逻辑
                 */
                if (k == key) {
    
    
                    //替换value。
                    e.value = value; 
					
                    //将过期的entry放到当前位置i,因为下面要从i这个位置开始清理
                    tab[i] = tab[staleSlot];
                    //将替换完毕的entry放到过期数据的位置
                    tab[staleSlot] = e;
					
                    //条件成立:说明replaceStaleEntry一开始时向前查找过期数据时,并未					  //找到过期的entry.
                    if (slotToExpunge == staleSlot)
                    //因为上面做了交换,所以当前位置i就是过期数据,赋值给slotToExpunge
                        slotToExpunge = i;
                    
                    //下面就是清理过期的entry。
                    cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
                    return;
                }
				
                /* 
                 * 条件1: k == null成立,说明当前遍历的entry是一个过期数据
                 * 条件2: slotToExpunge == staleSlot成立说明一开始向前查找过期数据				  *	并未找到过期的entry
                 */
                if (k == null && slotToExpunge == staleSlot)
                    //因为向后查询过程中查找到了一个过期数据,更新slotToExpunge为当前位					 //置,前提条件是前驱扫描时未发现过期数据
                    slotToExpunge = i;
            }
			
     
     		/*
     		 *  什么时候执行到这里?
     		 *—>向后查找过程中,并未发现 key = null 的entry,说明当前set操作是一个添加
     		 *  逻辑,直接将新数据添加到过期entry的位置上。
     		 */
            tab[staleSlot].value = null;
            tab[staleSlot] = new Entry(key, value);
			
     		//条件成立:说明除了当前staleSlot过期entry位置以外,还发现其他的过期slot了
            if (slotToExpunge != staleSlot)
                //开启清理数据的逻辑。
                cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
        }


//------------------ThreadLocalMap.cleanSomeSlots----------------------------
       /*
        * @param i 表示清理工作的起始位置,这个位置一定是NULL。
        * @param n 表示table.length
        */  
		private boolean cleanSomeSlots(int i, int n) {
    
    
            boolean removed = false;
            Entry[] tab = table;
            int len = tab.length;

            do {
    
    
                //获取当前i位置的下一个位置
                i = nextIndex(i, len);
                //获取位置上的entry。
                Entry e = tab[i];
                
                /*
                 * 条件成立表示当前位置的entry是过期数据,
                 * 需要清理  
                 */
                if (e != null && e.get() == null) {
    
    
                    n = len; 
                    //清理表示置为true,表示清理过
                    removed = true;
                    //以当前过期的slot位置开始,做一次探测式清理工作。
                    i = expungeStaleEntry(i);
                }
            
            /*
             * 表示循环次数。
             */                
            } while ( (n >>>= 1) != 0);
            return removed;
        }

5.set()方法流程图

在这里插入图片描述

6.rehash()方法

只有当前table元素个数大于等于扩容阈值并且在清理完table内部的所有过期的entry后,元素个数还大于等于阈值的3/4,这时才会触发扩容。

//--------------------ThreadLocalMap.rehash()---------------------------------
			
		private void rehash() {
    
    
            //这个方法执行完毕后,当前散列表内部所有过期的数据,都会被干掉。
            expungeStaleEntries();
            
            //条件成立,说明清理完所有的过期entry后,size数量仍然达到了扩容阈值的 3/4
            //才会去做一次resize()扩容。
            if (size >= threshold - threshold / 4)
                resize();
        }

//------------------------ThreadLocalMap.resize()----------------------------- 
        private void resize() {
    
    
            
            //扩容,变为原长度的2倍
            Entry[] oldTab = table;
            int oldLen = oldTab.length;
            int newLen = oldLen * 2; // 新长度 = 原长度 * 2;
            //创建一个新的table,
            Entry[] newTab = new Entry[newLen];
            
            //表示新表中的元素个数
            int count = 0;
			
            //遍历原表中的每一个slot,将原表中的数据迁移到新表。
            for (int j = 0; j < oldLen; ++j) {
    
    
                Entry e = oldTab[j];
                if (e != null) {
    
    
                    ThreadLocal<?> k = e.get();
                    if (k == null) {
    
    
                        e.value = null; // Help the GC
                    } else {
    
    
                        //计算新位置
                        int h = k.threadLocalHashCode & (newLen - 1);
                        //遍历找空位置(找到距离目标位置最近的一个slot)
                        while (newTab[h] != null)
                            h = nextIndex(h, newLen);
                        //放到新位置
                        newTab[h] = e;
                        count++;
                    }
                }
            }
            setThreshold(newLen); //设置新的扩容阈值
            size = count; // 将count赋值给size 
            table = newTab; //将新表赋值给table。
        }

//----------------------ThreadLcoalMap.remove()-------------------------------
    private void remove(ThreadLocal<?> key) {
    
    
            Entry[] tab = table;
            int len = tab.length;
        	//根据key获取索引位置
            int i = key.threadLocalHashCode & (len - 1);
            //遍历
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
    
    
                //找到指定key
                if (e.get() == key) {
    
                        
                    /*
                     * entry是弱引用,调用clear()方法会将内部关联的threadLocal置为					 * NULL 
                     */
                    e.clear();
                    //清理当前位置 将entry内部的value以及entry干掉。
                    expungeStaleEntry(i);
                    return;
                }
            }
        }

7.三大清理方法流程图

expungeStaleEntry运行流程

cleanSomeSlots运行流程

replaceStaleEntry运行流程

猜你喜欢

转载自blog.csdn.net/qq_46312987/article/details/121844970
今日推荐