0
点赞
收藏
分享

微信扫一扫

ThreadLocal原理解析




什么是ThreadLocal?



ThreadLocal是线程本地副本,就是每个线程都会有自己私有的副本值,互不干扰。典型的一种空间换时间的做法。



 



基本流程如下:



第一步:首先通过threadLocal.set("xxxx")获取当前线程副本值



--  大体流程:获取当前线程->拿到当前线程的threadLocals变量->根据map是否有来设置值还是创建值



第二步:通过threadLocal.get()直接获取当前线程的本地副本值



--  大体流程:获取当前线程->获取当前线程的threadLocals->获取当前threadLocals的hashcode,得到下标,然后去threadLocals的table数组获取值返回



 



其他,了解ThreadLocal与ThreadLocalMap与table[]数组关系



ThreadLocal是最外层,threadLocal可以根据对应线程获取当前线程的ThreadLocalMap,然后ThreadLocalMap里面包含一个Entry table[]数组(Entry<ThreadLocal<?>, Object>, 其中key的ThreadLocal类型是一个弱引用,容易被回收),数组的下标使用当前ThreadLocal的hashCode(key.threadLocalHashCode & (len-1))来定位出table[]数组下标,进而进行获取和更新



 



怎么避免hash冲突?



线性探测方式,采用步长+1的方式往下找



-- 获取下一个步长代码为:private static int nextIndex(int i, int len) {return ((i + 1 < len) ? i + 1 : 0);}



 



为什么使用开放地址法-线性探测 来处理ThreadLocalMap的hash冲突?



--  第一点:ThreadLocal存放的数量往往不会很大,同时会被回收,所以使用开放地址法数组会更快,更省空间。
 
  
--  第二点:ThreadLocal中用了一个HASH_INCREMENT = 0x61c88647,0x61c88647是一个很神奇的数字,能让hash均匀分配在2的N次方数组里
 
  
----    并且那次获取hashcode的时候都会通过如下方式获取,由于nextHashCode是AtomiInteger,所以每次获取都不一样
 
 
private final int threadLocalHashCode = nextHashCode();
 
 
private static AtomicInteger nextHashCode = new AtomicInteger();
 
 

   
 
 
 

  /**
 
 
 

  * 一个很神奇的数字,具体怎么神奇要自己探究了
 
 
 

  */
 
 
 
private static final int HASH_INCREMENT = 0x61c88647;
 
 

   
 
 
 

  /**
 
 
 

  * 返回下一个hashcode
 
 
 

  */
 
 
 
private static int nextHashCode() {
 
 
 
    return nextHashCode.getAndAdd(HASH_INCREMENT);
 
 

  }


 



static和非static有啥区别?



一般建议ThreadLocal使用static的方式,因为如果不用static,由于Entry的键是ThreadLocal,那就每次都要创建ThreadLocal实例。造成资源浪费。



 



怎么进行回收?避免内存泄漏?



1 设置ThreadLocalMap中table[]数组Entty<ThreadLocal<?>, Object>的键为弱引用



--  实现弱引用的代码:static class Entry extends WeakReference<ThreadLocalCode<?>> {.........}




remove()进行释放(其实就是赋值为null)


public void remove() {
 
 
    ThreadLocalMap m = getMap(Thread.currentThread());// 获取对应线程
 
 
    if (m != null)
 
 
        // 将this.referent = null、table[i].value=null、table[i]=null
 
 
        m.remove(this);
 
 
    }
 
 
}


3 如果不调用remove方法,每次调用get()、set(),都会顺便回收一下key为空的Entry


set()核心调用的回收方法逻辑为:
  
 
 
// k==null&&e!=null 说明key被垃圾回收了,这里涉及到弱引用,接下来讲
 
 
if (k == null) {
 
 
 
    // 被回收的话就需要替换掉过期的值,把新的值放在这里返回
 
 
, value, i);
 
 
    return;
 
 

  }



举个ThreadLocal set()/get()代码如下:

ThreadLocal<String> sThreadLocalThread = new ThreadLocal<>();
  
 
  

   sThreadLocalThread.set("设置个值");// 设置线程本地副本值
  
 
  

   sThreadLocalThread.get();// 获取线程本地副本值



第一步:首先通过threadLocal.set("xxxx")获取当前线程副本值


public void set(T value) {
 
 
    // 获取当前线程
 
 
    Thread t = Thread.currentThread();
 
 
    // 拿到当前线程的ThreadLocalMap对象,实际就是返回当前线程的threadLocals变量t.threadLocals
 
 
    ThreadLocalMap map = getMap(t);
 
 
    // this作key传入存储value
 
 
    if (map != null)
 
 
        map.set(this, value);
 
 
    else
 
 
        createMap(t, value);// t.threadLocals = new ThreadLocalMap(this, firstValue)
 
 
}
 
 
// 上面map.set(this, value)调用如下
 
 
private void set(ThreadLocal<?> key,
 
 
table;
 
 
    int len = tab.length;
 
 
    //计算数组的下标
 
 
    int i = key.threadLocalHashCode & (len-1);
 
 

   
 
 
 
    //注意这里结束循环的条件是e != //null,这个很重要,还记得上面讲的开放地址法吗?忘记的回到上面看下,一定看懂才往下走,不然白白浪费时间
 
 
    //这里遍历的逻辑是,先通过hash 找到数组下标,然后寻找相等的ThreadLocal对象
 
 
    //找不到就往下一个index找,有两种可能会退出这个循环
 
 
    // 1.找到了相同ThreadLocal对象
 
 
    // 2.一直往数组下一个下标查询,直到下一个下标对应的是null 跳出
 
 
    for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
 
 
;
 
 
        //如果找到直接设置value 值返回,这个很简单没什么好讲的
 
 
        if
 
 
value = value;
 
 
            return;
 
 

          }
 
 
 

   
 
 
 
        // k==null&&e!=null 说明key被垃圾回收了,这里涉及到弱引用,接下来讲
 
 
        if (k == null) {
 
 
 
            //被回收的话就需要替换掉过期的值,把新的值放在这里返回
 
 
, value, i);
 
 
            return;
 
 

          }
 
 
 

      }
 
 
 
    //来到这里,说明没找到
 
 
new Entry(key, value);
 
 
    int sz = ++size;
 
 
    // 查找已被gc回收的对象,并清除entry
 
 
    // 如果没有清除任何entry,并且当前使用量达到了负载因子所定义(长度的2/3),那么进行rehash()
 
 
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
 
 
 
;
 
 
}



第二步:通过threadLocal.get()直接获取当前线程的本地副本值

public T get() {
 
 
    // 获取当前线程
 
 
    Thread t = Thread.currentThread();
 
 
    // 拿到当前线程的ThreadLocalMap对象:t.threadLocals
 
 
    ThreadLocalMap map = getMap(t);
 
 
    if (map != null) {
 
 
        //因为ThreadLocalMap存的时候就是拿ThreadLocal的hashcode作key.
 
 
        //因此这里传入this获取entry,再拿到value
 
 
        ThreadLocalMap.Entry e = map.getEntry(this);
 
 
        if (e != null) {
 
 
            @SuppressWarnings("unchecked")
 
 
            T result = (T)e.value;
 
 
            return result;
 
 
        }
 
 
    }
 
 
    return setInitialValue();
 
 
}

参考链接


                  https://www.jianshu.com/p/9eccf491e5e5

 



 

举报

相关推荐

0 条评论