当前位置:首页 > java基础 > 正文内容

理解TheadLocal源码

淙嶙5年前 (2020-07-21)java基础1031

记忆会被改变,以前看过的代码,现在发现和记忆中的不一样,大脑会修改记忆的确是真的. 那就重复的看下,记录下吧.

作用: 为每个使用该变量的线程创建一个独立的变量副本.在多线程环境,就能避免变量被篡改问题. 重要的方法:

protected T initialValue() ;

public T get();

public void set(T value);

public void remove();

我们在程序中使用方式:

ThreadLocal<Integer> threadLocal =  new ThreadLocal<>();//or ThreadLocal.withInitial(() -> 1);
threadLocal.set(2);

设置值的分析:

//分割 -----
//TheadLocal中无参构造方法是个空方法.见下
public ThreadLocal() {
}
//TheadLocal中的set方法. 
public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);//map不为空 直接放入值
    else
        createMap(t, value);//创建ThreadLocalMap 并设置第一个值
}
//首次ThreadLocalMap为空, 创建ThreadLocalMap 并设置第一个值
void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
    table = new Entry[INITIAL_CAPACITY];//INITIAL_CAPACITY=16
    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
    table[i] = new Entry(firstKey, firstValue);
    size = 1;
    setThreshold(INITIAL_CAPACITY);//设置扩容的阀值
}
//若是ThreadLocalMap不为空, 在数组中放置值的过程, 1.相同key 覆盖,2.key为null 替换,3.创建新Entry
private void set(ThreadLocal<?> key, Object value) {
    Entry[] tab = table;
    int len = tab.length;
    int i = key.threadLocalHashCode & (len-1);

    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();//扩容.
}

取值的分析:

//ThreadLocal 取值,1.获取线程对应的ThreadLocalMap 2.map取对应的Entry
public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}
//从ThreadLocalMap中取对应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);//存在hash冲突时,需要遍历取
}
//由于hash冲突等 首次取值失败,遍历取值.
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
    Entry[] tab = table;
    int len = tab.length;

    while (e != null) {
        ThreadLocal<?> k = e.get();
        if (k == key)
            return e;
        if (k == null)
            expungeStaleEntry(i);
        else
            i = nextIndex(i, len);
        e = tab[i];
    }
    return null;
}

删除值–匹配删除.

private void remove(ThreadLocal<?> key) {
    Entry[] tab = table;
    int len = tab.length;
    int i = key.threadLocalHashCode & (len-1);
    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {
        if (e.get() == key) {//只有key为同一个才删除
            e.clear();
            expungeStaleEntry(i);
            return;
        }
    }
}

总结: ThreadLocal 其实是一个线程级的Entry数组(即:每个线程里都有一个ThreadLocal.ThreadLocalMap类型的threadLocals变量),每次设置值或取值时,先拿到当前线程的这个变量.然后对变量(Entry数组)进行操作. 说的再通俗一点就是: ThreadLocal 是属于线程的变量,定义在Thread中的.所以是线程私有的. ps:(ThreadLocalMap)Entry数组是存放线程级的变量的地方,使用数组存储就需要考虑hash冲突的问题,这里处理hash冲突跟HashMap是不同的,HashMap是链地址法,ThreadLocalMap是开放定址法-线性探测再散列.

相关文章

指令重排

指令重排

Happen-Before先行发生规则如果光靠sychronized和volatile来保证程序执行过程中的原子性, 有序性, 可见性, 那么代码将会变得异常繁琐. JMM提供了Happen-Bef...

ConcurrentHashMap源码解读

ConcurrentHashMap源码解读

/* * Written by Doug Lea with assistance from members of JCP JSR-166 * Expert Group and released...

网络知识汇总

网络知识汇总

传输层概述 作用:传输层为它上面的应用层提供通信服务。 在OSI七层参考模型中,传输层是面向通信的最高层,也是用户功能的最底层。 传输层两大重要的功能:复用 和 分用。 复用:在发送端,多个应用进程公...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。