ThreadLocal

ThreadLocal

ThreadLocal存储当前线程私有的数据

Thread类中有一个默认(包级别)访问权限的字段:ThreadLocals,它是ThreadLocalMap类型的。

ThreadLocal.ThreadLocalMap threadLocals = null;

ThreadLocalMap是ThreadLocal的静态内部类。key是弱引用

set操作

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);//以ThreadLocal对象为Key,value为值
    else
        createMap(t, value);
}

其中主要的操作:

  1. 获取当前线程的ThreadLocalMap对象:ThreadLocalMap map = getMap(t);

    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
    
  2. 在map中设置当前的key和value,其实就是在table数组的对应位置放置一个Entry,这个位置是通过key的哈希值与数组长度取模确定的,如果发生冲突,就探测下一个位置。

   private void set(ThreadLocal<?> key, Object value) {
          Entry[] tab = table;
          int len = tab.length;
          int i = key.threadLocalHashCode & (len-1);  
  	//如果这个位置已经存在Entry对象:
  		//1.如果存在的Key就是要set的key,那么更新对应的value值
  		//2.如果key==null,这是因为在Entry中弱引用的ThreadLocal已经被垃圾回收,但是还存在旧的value没有清理,需要替换旧的Entry
  		//否则,就采用开放地址法,探测下一个位置(源码其实就是i+1):i = nextIndex(i, len)是否还是e!=null,进行下一轮循环,直到e==null
  	//如果不存在Entry对象,就新建Entry(key,value)
      for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {			
          ThreadLocal<?> k = e.get();
  
          if (k == key) {
              e.value = value;
              return;
          }
  
          if (k == null) {
              replaceStaleEntry(key, value, i);
              return;
          }
      }
  
      tab[i] = new Entry(key, value);
      int sz = ++size;
      if (!cleanSomeSlots(i, sz) && sz >= threshold)
          rehash();
  }
  1. 如果不存在ThreadLocalMap,就新建Map

    • ThreadLocalMap里面的table数组的负载因子是2/3,如果超过threshold,会先清理无用的Entry,如果还不够就两倍扩容
    private void setThreshold(int len) {
        threshold = len * 2 / 3;
    }
    

get操作

public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);\\1.
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);\\2.
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;\\3.
            return result;
        }
    }
    return setInitialValue();
}

其中的主要操作:

  1. 获取当前线程对应的ThreadLocalMap:ThreadLocalMap map = getMap(t);

    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
    
  2. 根据ThreadLocalMap的getEntry(key)方法,以当前的ThreadLocal作为key获取Entry对象。在ThreadLocalMap类中有一个Entry类型的数组,数组每个位置都是Entry类型的数据,这里就是根据Key的哈希码与数组长度取模得到当前Key在数组中的位置,获取对应的Entry对象。

    private Entry getEntry(ThreadLocal<?> key) {
        int i = key.threadLocalHashCode & (table.length - 1);
        Entry e = table[i];
        if (e != null && e.get() == key)
            return e;
        else
            return getEntryAfterMiss(key, i, e);
    }
    
  3. Entry的value就是需要的值:T result = (T)e.value;

总结:

  • Thread类中有ThreadLocalMap类型的属性ThreadLocals,而ThreadLocalMap是ThreadLocal的静态内部类,它内部有一个静态内部类Entry,继承于WeakReference,Entry里面的key就是弱引用的ThreadLocal对象,value是设定的值。有一个Entry类型的table数组用于存放所有线程对应的Entry,当前线程对应的Entry在数组中的位置是通过key的哈希值与数组长度取模确定的。set和get方法就是从table数组中合适的位置放入Entry或者是获取value。

弱引用问题:

每个Thread内部都维护一个ThreadLocalMap数据结构,Map的Key就是ThreadLocal,ThreadLocal内部存储实体结构Entry<ThreadLocal, T>继承自java.lan.ref.WeakReference,这样当ThreadLocal不再被引用时,因为弱引用机制原因,垃圾回收时,jvm会自动回收弱引用指向的实例内存,即其线程内部的ThreadLocalMap会释放其对ThreadLocal的引用回收ThreadLocal对象,而非整个Entry,所以线程变量中的值T对象还是在内存中存在的,所以内存泄漏的问题还没有完全解决。应该尽量用threadLocal.remove()来清除无用的Entry。

posted @ 2021-06-18 22:02  keepkeep  阅读(39)  评论(0编辑  收藏  举报