美文网首页
ThreadLocal

ThreadLocal

作者: outwar | 来源:发表于2018-09-17 16:59 被阅读0次

ThreadLocal在项目中使用到的情况比较少,只知道可以做到线程隔离。如果某个对象可能产生并发错误,那么常规解决方案就是对其加锁,另一种思路就是使用ThreadLocal保证线程隔离。

首先查看ThreadLocal的get方法:

public T get() {
    //当前线程
    Thread t = Thread.currentThread();
    //当前线程持有的ThreadLocalMap
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        //得到ThreadLocalMap中键为当前ThreadLocal的条目
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

简单的理解就是,一个线程和一个ThreadLocal对应了一个唯一的对象。

继续深入查看下去,getMap返回的就是线程对象持有的ThreadLocalMap,这个类虽然没有继承Map接口,但是现在可以把它当作map来看待,它的键值是ThreadLocal类。当这个map不为空时,就从它取出来数据,当它为空时,调用setInitialValue方法。

private T setInitialValue() {
    //initialValue默认返回null
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        //设置默认的初始值
        map.set(this, value);
    else
        //创建ThreadLocalMap插入默认初始值并设置给当前线程t
        createMap(t, value);
    return value;
}

ThreadLocalMap

之前说了ThreadLocalMap并不是Map,它是ThreadLocal的内部静态类,内部存储的数据结构是一个Entry数组。Entry继承自WeakReference<ThreadLocal<?>>,并持有一个value对象。

现在的问题就是ThreadLocalMap是怎么通过一个ThreadLocal对象来找到对应的Entry数组下标?

重新查看createMap方法,调用了ThreadLocalMap的一个构造方法。ThreadLocalMap共有两个构造方法,另一个private的构造方法是为了InheritableThreadLocal设计的,此处不介绍。查看源码:

ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
    //初始化Entry数组,大小为16
    table = new Entry[INITIAL_CAPACITY];
    //计算得到下标
    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
    //创建条目放入该下标位置
    table[i] = new Entry(firstKey, firstValue);
    size = 1;
    //设置临界值为数组长度的2/3
    setThreshold(INITIAL_CAPACITY);
}

介绍一下这个计算的方法,threadLocalHashCode是每个ThreadLocal对象初始化时得到的一个值,这个值是0x61c88647的倍数。INITIAL_CAPACITY是数组的长度,通过保证数组长度永远是2的n次方,从而保证其减去一得到的二进制是一串全部为1长度为n的数字,与其做&操作就可以理解为取二进制最后n位。而0x61c88647,与fibonacci hashing(斐波那契散列法)以及黄金分割有关,特殊的哈希码0x61c88647大大降低碰撞的几率,能让哈希码能均匀的分布在2的N次方的数组里。

long l = (long) ((1L << 31) * (Math.sqrt(5) - 1));  //Math.sqrt(5) - 1 = 1.2360679774997898
System.out.println("as 32 bit unsigned: " + l);  //2654435769
        
int i = (int) l;
System.out.println("as 32 bit signed:   " + i);  //-1640531527 = -0x61c88647

查看ThreadLocalMap的getEntry方法,发现就是通过该计算方式来得到数组下标的:

 private Entry getEntry(ThreadLocal<?> key) {
    int i = key.threadLocalHashCode & (table.length - 1);
    Entry e = table[i];
    //条目非空且弱引用的ThreadLocal等于入参
    if (e != null && e.get() == key)
        return e;
    else
        return getEntryAfterMiss(key, i, e);
}

相关文章

网友评论

      本文标题:ThreadLocal

      本文链接:https://www.haomeiwen.com/subject/lsxpcftx.html