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冲突?
ThreadLocalMap解决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 nextHashCodenew 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<?>> {.........}
 
2 每次使用完threadLocal需要手动调用remove()进行释放(其实就是赋值为null)
public void remove() {
    ThreadLocalMap m = getMap(Thread.currentThread());// 获取对应线程
    if (m != null)
        // this.referent = nulltable[i].value=nulltable[i]=null
        m.remove(this);
    }
}
3 如果不调用remove方法,每次调用get()、set(),都会顺便回收一下key为空的Entry
--  set()核心调用的回收方法逻辑为:
// k==null&&e!=null 说明key被垃圾回收了,这里涉及到弱引用,接下来讲
if (k == null) {
    // 被回收的话就需要替换掉过期的值,把新的值放在这里返回
    replaceStaleEntry(key, 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);
    // thiskey传入存储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, Object value) {    
    Entry[] tab = 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)]) {
        ThreadLocal<?> k = e.get();
        //如果找到直接设置value 值返回,这个很简单没什么好讲的
        if (k == key) {
            e.value = value;
            return;
        }
 
        // k==null&&e!=null 说明key被垃圾回收了,这里涉及到弱引用,接下来讲
        if (k == null) {
            //被回收的话就需要替换掉过期的值,把新的值放在这里返回
            replaceStaleEntry(key, value, i);
            return;
        }
    }
    //来到这里,说明没找到
    tab[i] = new Entry(key, value);
    int sz = ++size;
    // 查找已被gc回收的对象,并清除entry
    // 如果没有清除任何entry,并且当前使用量达到了负载因子所定义(长度的2/3),那么进行rehash()
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}
 
第二步:通过threadLocal.get()直接获取当前线程的本地副本值
public T get() {
    // 获取当前线程
    Thread t = Thread.currentThread();
    // 拿到当前线程的ThreadLocalMap对象:t.threadLocals
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        //因为ThreadLocalMap存的时候就是拿ThreadLocalhashcodekey.
        //因此这里传入this获取entry,再拿到value
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}
 
 
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值