ThreadLocal源码阅读

104 阅读11分钟

ThreadLocal简介

  • ThreadLocal类为线程提供一份独立的本地变量,这些本地变量可以通过set(),get()操作

  • 线程Thread对象中有一个ThreadLocalMap,这个Map的key就是ThreadLocal对象,value就是本地变量

		
	/* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;
	
 
 
  • 当我们使用ThreadLocal的set()方法时,实际上就是把当前ThreadLocal对象和本地变量放入线程的ThreadLocalMap中

  • 当我们使用ThreadLocal的get()方法时,实际上就是从线程的ThreadLocalMap中,取出以当前ThreadLocal对象为key的value值

  • ThreadLocal对象和本地变量应该放入ThreadLocalMap哪个位置?是通过ThreadLocal对象的threadLocalHashCode计算的

  • threadLocalHashCode这个值在每次创建ThreadLocal对象时都会增加一个哈希增量,降低碰撞概率

		
	//通过nextHashCode()计算
	 private final int threadLocalHashCode = nextHashCode();

    //保证原子性更新
    private static AtomicInteger nextHashCode = new AtomicInteger();

    //HASH_INCREMENT:哈希增量
    private static final int HASH_INCREMENT = 0x61c88647;

    //每创建一个ThreadLocal对象,nextHashCode这个值就会增长0x61c88647  
    private static int nextHashCode() {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }
	
 
 

ThreadLocalMap属性

  • ThreadLocalMap是一个哈希表,key保存的是ThreadLocal对象,value保存的本地变量

  • key为什么使用弱引用?

  • 当ThreadLocal对象失去强引用,由于key是弱引用此时只要有GC就会回收ThreadLocal对象

  • 哈希表中的与ThreadLocal对象相关联的Entry再次去entry.get()时,此时拿到的key是null

        站在ThreadLocalMap的角度就可以分辨出哪些Entry是过期的,哪些Entry是非过期的
    
  • ThreadLocalMap会清理过期的Entry

  • ThreadLocalMap发生哈希冲突时,采用线性探测法:从冲突位置一直向后找一个可用位置

		
	//哈希表结点
	static class Entry extends WeakReference> {
		   
		Object value;

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

	//初始化的哈希表长度:16,必须是2的次方    
	private static final int INITIAL_CAPACITY = 16;

	//哈希表  
	private Entry[] table;

	//哈希表存放的结点个数    
	private int size = 0;

	//扩容阈值    
	private int threshold;

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

	//返回上一个位置
	private static int prevIndex(int i, int len) {
		
		//如果小于0,就回到最后
		return ((i - 1 >= 0) ? i - 1 : len - 1);
	}

	//返回下一个位置
	private static int nextIndex(int i, int len) {
		
		//如果超过长度从0开始
		return ((i + 1 < len) ? i + 1 : 0);
	}

	//只有在线程第一次存储本地变量时才会创建ThreadLocalMap对象
	ThreadLocalMap(ThreadLocal firstKey, Object firstValue) {
			
		//创建哈希表长度为16
		table = new Entry[INITIAL_CAPACITY];
		
		//找到key对应的哈希表中的位置
		int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);

		//创建Entry对象存放到指定哈希表的指定位置
		table[i] = new Entry(firstKey, firstValue);
		
		//此时哈希表中只有一个结点
		size = 1;
		
		//设置扩容阈值:哈希表长度的三分之二
		setThreshold(INITIAL_CAPACITY);
	}
	

解析ThreadLocalMap的getEntry()和getEntryAfterMiss()

  • getEntry()方法是ThreadLocalMap用来根据key获取value的

  • getEntryAfterMiss()方法会继续向当前位置后面搜索 e.key == key 的Entry

		
	private Entry getEntry(ThreadLocal key) {
	   
		//计算key对应的哈希表位置
		int i = key.threadLocalHashCode & (table.length - 1);
		
		//访问哈希表中指定位置的Entry
		Entry e = table[i];
		
		//条件一:e有值
		//条件二:e的key与当前查询的key一致
		if (e != null && e.get() == key)
			return e;
		else
			
			//此时e为空,或者e的key不是我们要找的key
			return getEntryAfterMiss(key, i, e);
	}
	
	private Entry getEntryAfterMiss(ThreadLocal key, int i, Entry e) {
		
		//当前哈希表
		Entry[] tab = table;
		
		//哈希表长度
		int len = tab.length;

		//e:循环处理的结点
		while (e != null) {
			
			//当前位置中Entry对象的key
			ThreadLocal k = e.get();
			
			//找到了
			if (k == key)
				return e;
			
			//ThreadLocal对象已经被GC回收了,因为key 是弱引用
			if (k == null)
				
				//删去过期数据,具体后面会写
				expungeStaleEntry(i);
			else
				
				//更新index,继续向后搜索
				i = nextIndex(i, len);
				
			//获取下一个Entry结点
			e = tab[i];
		}

		//没找到
		return null;
	}
	

解析ThreadLocalMap的set()

  • set()方法是把key-value键值对放入ThreadLocalMap,如果已经有这个key就是替换,否则就是增加
		
	private void set(ThreadLocal key, Object value) {
	
		//哈希表
		Entry[] tab = table;
		
		//哈希表长度
		int len = tab.length;
		
		//计算当前key在哈希表中的对应的位置
		int i = key.threadLocalHashCode & (len-1);

		//线性探测:找到key可以使用的位置,或者找到已经存在的key进行替换
		for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {

			//获取当前元素key
			ThreadLocal k = e.get();

			//已经存在key,当前set操作是一个替换操作。
			if (k == key) {
				
				//做替换操作
				e.value = value;
				return;
			}

			//向后寻找过程中key == null,说明当前Entry是过期数据
			if (k == null) {
				
				//这个位置的Entry已经过期,使用这个位置,有可能是替换或者增加,具体后面会写
				replaceStaleEntry(key, value, i);
				return;
			}
		}

		//找到了可以使用的位置,创建一个新的Entry对象
		tab[i] = new Entry(key, value);
		
		//新添加,所以size + 1
		int sz = ++size;

		//进行一次启发式清理,具体后面会写
		//条件一:启发式清理工作未清理到任何数据
		//条件二:当前哈希表内的结点数目已经达到扩容阈值
		if (!cleanSomeSlots(i, sz) && sz >= threshold)
			
			//这个方法后面会写
			rehash();
	}
	
 
 

解析ThreadLocalMap的replaceStaleEntry()

  • 当set()方法放置key-value键值对时,线性探测到一个过期数据的位置,使用这个位置进行set()操作

        
      //staleSlot这个位置上的结点是过期结点
      private void replaceStaleEntry(ThreadLocal key, Object value, int staleSlot) {
      	
      	//哈希表
      	Entry[] tab = table;
      	
      	//哈希表长度
      	int len = tab.length;
      	
      	//临时变量
      	Entry e;
    
      	//开始探测式清理过期数据的位置
      	//关于探测式清理expungeStaleEntry(),后面会写
      	int slotToExpunge = staleSlot;
    
    
      	//以当前staleSlot向前查找,找过期的数据
      	for (int i = prevIndex(staleSlot, len); (e = tab[i]) != null; i = prevIndex(i, len)){
      		
      		//向前找到了过期数据,更新探测清理过期数据的位置为i
      		if (e.get() == null){
      			slotToExpunge = i;
      		}
      	}
      	
      	//为什么还要向后查找,不直接使用过期位置staleSlot呢?
      	//因为相同的key都是连在一起的,还需要向后查找是不是有这个key
      	
      	//以当前staleSlot向后查找,有可能是替换或者增加
      	for (int i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) {
      		
      		//获取当前结点key
      		ThreadLocal k = e.get();
    
      		//找到了key,替换
      		if (k == key) {
      			
      			//替换新数据
      			e.value = value;
    
      			//下面这两步操作实际上是交换了staleSlot位置和i位置的数据
      			//这是因为staleSlot位置是过期数据,把我们未过期的数据放到那,减少了get()操作是向后线性探测的时间
      			tab[i] = tab[staleSlot];
      			tab[staleSlot] = e;
    
      			// 前面的向前查找过期数据,并未找到过期的Entry
      			// 下面的向后查找过期数据,也未找到过期的Entry
      			if (slotToExpunge == staleSlot)
      				
      				//当前位置i存放的就是过期数据,更新探测清理过期数据的位置为i
      				slotToExpunge = i;
    
      			//cleanSomeSlots:启发式清理,具体后面会写
      			//探测式清理expungeStaleEntry():slotToExpunge是过期数据的位置
      			//expungeStaleEntry():会从slotToExpunge位置开始向后清理过期数据,直到未过期的位置
      			cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
      			return;
      		}
    
      		//条件一:当前遍历的Entry是一个过期数据
      		//条件二:前面的向前查找过期数据,并未找到过期的Entry
      		if (k == null && slotToExpunge == staleSlot)
      			
      			//更新探测清理过期数据的位置为当前位置
      			slotToExpunge = i;
      	}
    
      	//什么时候执行到这里呢?
      	//并未发现 k == key 的Entry,可以使用过期位置staleSlot
    
      	//直接将新数据添加到staleSlot这个位置
      	tab[staleSlot].value = null;
      	tab[staleSlot] = new Entry(key, value);
    
      	//前面查找到了过期的Entry,开启清理数据
      	if (slotToExpunge != staleSlot)
      		cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
      }
      
    
    

解析ThreadLocalMap的expungeStaleEntry()

  • expungeStaleEntry()用来探测过期数据并清除,从staleSlot位置开始,一直探测到Entry为null的位置

  • 探测到非过期数据时,会把这个结点放到更正确的位置

		
	private int expungeStaleEntry(int staleSlot) {
		
		//哈希表
		Entry[] tab = table;
		
		//哈希表长度
		int len = tab.length;

		//清除当前位置value
		tab[staleSlot].value = null;
		
		//因为staleSlot位置的Entry是过期的,直接清除
		tab[staleSlot] = null;
		
		//上面Entry被清空了
		size--;

		//e:表示当前遍历结点
		Entry e;
		
		//i:表示当前遍历的位置
		int i;

		//for循环从 staleSlot + 1的位置开始探测过期数据,直到碰到Entry为空结点。
		for (i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) {
			
			//获取当前遍历结点Entry的key
			ThreadLocal k = e.get();

			//k表示的ThreadLocal对象已经被GC回收了,当前Entry过期
			if (k == null) {
				
				//清除当前位置value
				e.value = null;
				
				//过期了,清除
				tab[i] = null;
				
				//上面Entry被清空了
				size--;
			} else {
				//当前遍历的位置中Entry是非过期数据,下面会把当前遍历位置的Entry放到更合适的位置

				//重新计算当前Entry对应的位置
				int h = k.threadLocalHashCode & (len - 1);
				
				//当前Entry存储时,发生过hash冲突,被向后偏移过了
				if (h != i) {
					
					//将当前位置设为null
					tab[i] = null;

					//以正确位置h开始,查找第一个可以存放Entry的位置
					while (tab[h] != null)
						h = nextIndex(h, len);

					//将当前结点放入到距离正确位置最近的位置
					tab[h] = e;
				}
			}
		}
		//返回的位置为空结点
		return i;
	}
	
 
 

解析ThreadLocalMap的cleanSomeSlots()

  • cleanSomeSlots(expungeStaleEntry(slotToExpunge), len)中expungeStaleEntry(slotToExpunge)是Entry为空的位置,len是长度

  • cleanSomeSlots(i, sz)中i是被新添加的Entry位置,sz为哈希表Entry数目

		
	private boolean cleanSomeSlots(int i, int n) {
		
		//是否清除过过期数据
		boolean removed = false;
		
		//哈希表
		Entry[] tab = table;
		
		//哈希表长度
		int len = tab.length;

		do {
			//这里为什么不是从i就检查呢?
			//因为传进来i这个位置Entry为空,或者是新添加的,不是过期数据
			//下面循环中i也是探测清理返回的Entry为空的位置

			//获取当前i的下一个
			i = nextIndex(i, len);
			
			//获取哈希表中i位置的Entry
			Entry e = tab[i];
			
			//条件一:Entry不为空
			//条件二:Entry是一个过期的数据
			if (e != null && e.get() == null) {
				
				//重新更新n为哈希表长度
				n = len;
				
				//表示清理过数据
				removed = true;
				
				//以当前过期的Entry为开始做一次探测式清理
				i = expungeStaleEntry(i);
			}

				//n无符号右移
		} while ( (n >>>= 1) != 0);

		return removed;
	}
	
 
 

解析ThreadLocalMap的remove()

  • remove()方法是清除key所对应的Entry
		
	private void remove(ThreadLocal key) {
		
		//哈希表
		Entry[] tab = table;
		
		//哈希表长度
		int len = tab.length;
		
		//计算当前key在哈希表中的对应的位置
		int i = key.threadLocalHashCode & (len-1);
		
		//线性探测:从当前位置向后查找key
		for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
			
			//找到了
			if (e.get() == key) {
				
				//清除key
				e.clear();
				
				//探测式清除
				expungeStaleEntry(i);
				return;
			}
		}
	}
	
 
 

解析ThreadLocalMap的rehash()和resize()

  • rehash()方法会清除所有的过期Entry,清理完后根据阈值判断是否扩容

  • resize()方法就是扩容,迁移所有的Entry

		
	private void rehash() {
		
		//清除所有过期Entry,下面具体写这个方法
		expungeStaleEntries();

		//清理完过期数据后,当前散列表内的Entry数量仍然达到了扩容阈值的四分之三
		if (size >= threshold - threshold / 4)
			
			//扩容
			resize();
	}

	private void expungeStaleEntries() {
		
		//哈希表
		Entry[] tab = table;
		
		//哈希表长度
		int len = tab.length;
		
		//遍历整个哈希表
		for (int j = 0; j < len; j++) {
			
			//当前的Entry结点
			Entry e = tab[j];
			
			//结点已过期
			if (e != null && e.get() == null)
				
				//探测式清除
				expungeStaleEntry(j);
		}
	}
	
	//扩容
	private void resize() {
		
		//旧的哈希表
		Entry[] oldTab = table;
		
		//旧的哈希表长度
		int oldLen = oldTab.length;
		
		//扩容后的表长度
		int newLen = oldLen * 2;
		
		//新的哈希表
		Entry[] newTab = new Entry[newLen];
		
		//表示新哈希表中的Entry数量
		int count = 0;

		//遍历旧表迁移数据到新表
		for (int j = 0; j < oldLen; ++j) {
			
			//旧表Entry结点
			Entry e = oldTab[j];
			
			//旧表Entry结点中有数据
			if (e != null) {
				
				//旧表Entry结点中的key
				ThreadLocal k = e.get();
				
				//旧表Entry结是一个过期数据
				if (k == null) {
					e.value = null; // Help the GC
				} else {
					
					//计算出当前Entry在扩容后的新表的存储
					int h = k.threadLocalHashCode & (newLen - 1);
					
					//找到一个距离h最近的一个可以使用的位置
					while (newTab[h] != null)
						h = nextIndex(h, newLen);

					//将数据存放到新表的位置中
					newTab[h] = e;
					
					//数量+1
					count++;
				}
			}
		}

		//设置下一次触发扩容的指标
		setThreshold(newLen);
		size = count;
		
		//设置哈希表为新表
		table = newTab;
	}
	

解析ThreadLocal的set()

  • set()方法用来设置与当前线程关联的本地变量

        
      public void set(T value) {
      	
      	//当前线程
      	Thread t = Thread.currentThread();
      	
      	//当前线程的ThreadLocalMap对象
      	ThreadLocalMap map = getMap(t);
      	
      	//当前线程的ThreadLocalMap已经初始化过
      	if (map != null)
      		
      		//调用ThreadLocalMap.set方法进行重写或者添加,上面写过了
      		map.set(this, value);
      	else
      		
      		//当前线程还未创建ThreadLocalMap对象,需要为当前线程创建ThreadLocalMap
      		createMap(t, value);
      }
      
      ThreadLocalMap getMap(Thread t) {
      	
          //返回当前线程的threadLocals
          return t.threadLocals;
      }
      
      void createMap(Thread t, T firstValue) {
          
          //创建一个ThreadLocalMap对象给当前线程
          t.threadLocals = new ThreadLocalMap(this, firstValue);
      }
      
    
    

解析ThreadLocal的get()和setInitialValue()

  • get()方法用来获取与当前线程关联的本地变量
		
	public T get() {
		
		//当前线程
		Thread t = Thread.currentThread();
		
		//当前线程的ThreadLocalMap对象
		ThreadLocalMap map = getMap(t);
		
		//当前线程已经拥有自己的ThreadLocalMap 
		if (map != null) {
			
			//调用ThreadLocalMap.getEntry方法拿到存放当前ThreadLocal对象的Entry,上面写过了
			ThreadLocalMap.Entry e = map.getEntry(this);
			
			//Entry不为空
			if (e != null) {
				@SuppressWarnings("unchecked")
				T result = (T)e.value;
				//返回value
				return result;
			}
		}
		
		//当前线程没有有自己的ThreadLocalMap,或者该Map中没有存放当前ThreadLocal对象的Entry
		//下面具体写这个方法
		return setInitialValue();
    }
	
 
 
  • setInitialValue()用来设置当前ThreadLocal对象与当前线程相关联的本地变量

  • 这个本地变量是在initialValue()方法中初始化的,一般会重写这个方法

		
	private T setInitialValue() {
		
        //初始化本地变量
        T value = initialValue();
		
        //当前线程
        Thread t = Thread.currentThread();
		
        //当前线程的ThreadLocalMap对象
        ThreadLocalMap map = getMap(t);
		
        //当前线程已经拥有自己的ThreadLocalMap 
        if (map != null)
			
            //保存当前ThreadLocal和上面初始化的本地变量
            map.set(this, value);
			
        else
			
            //当前线程还未创建ThreadLocalMap对象,需要为当前线程创建ThreadLocalMap
            createMap(t, value);

        //初始化的本地变量
        return value;
    }
	
	//默认返回null,一般会重写这个方法
	protected T initialValue() {
        return null;
    }
	
 
 

解析ThreadLocal的remove()

  • remove()方法用来移除与当前线程关联的本地变量
		
	public void remove() {
		
         //当前线程的ThreadLocalMap对象
         ThreadLocalMap m = getMap(Thread.currentThread());
		 
         //当前线程已经拥有自己的ThreadLocalMap 
         if (m != null)
			 
             ////调用ThreadLocalMap.remove方法移除当前ThreadLocal对象的Entry,上面写过了
             m.remove(this);
    }