ThreadLocal如何起作用的

首先,ThreadLocal是用来进行线程间的数据隔离的。我们知道一般对于共享数据的安全防护是用锁来实现的,这里我们看看ThreadLocal是怎么实现线程隔离从而保证数据安全的。

其起作用的机制是为每个线程提供一个独立的变量副本。

我们看是怎么做到的

1、在Thread类当中,有如下代码

ThreadLocal.ThreadLocalMap threadLocals = null;

也就是说每个线程Thread都会持有一个ThreadLocalMap引用。

2、ThreadLocalMap

ThreadLocalMap是ThreadLocal当中的一个静态内部类,其实就是记录以ThreadLocal为键,要进行隔离的变量对象为值的一个Map映射,从其名称上也可以看出来。

......
// 由Entry表示的键值对
static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;

    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}
......
// 用一个Entry数组来记录同一线程下的多个ThreadLocal变量
private Entry[] table;

3、然后就是ThreadLocal中的几个重要方法get、set等

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();
}

其中getMap操作就拿到了1中说的线程的ThreadLocalMap对象,这里的线程是当前线程。

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

如果是线程的ThreadLocalMap对象还未创建,则进行创建,可以看出创建时传入的是this(ThreadLocal)。

private T setInitialValue() {
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
    return value;
}
// ThreadLocalMap的创建
void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

再看看set方法,set就是把ThreadLocal和与之对应的值组成的Entry放入到2中 Entry[] table 中具体位置的过程

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

4、同一线程Thread中使用到多个ThreadLocal的处理(多个不同类型的变量)

前边我们看到,每个线程Thread都持有一个ThreadLocalMap变量,然后通过ThreadLocalMap的Entry[] table来存储每一个ThreadLocal和值对象组成的Entry。多个ThreadLocal变量怎么处理,关键是看放到了哪个具体的位置,看以下关键代码

Entry[] tab = table;
int len = tab.length;
// 计算出该ThreadLocal变量应该存放在Entry[] table数组当中的哪个位置
int i = key.threadLocalHashCode & (len-1);

在当前线程中,每次new ThreadLocal的时候,threadLocalHashCode的值均会以HASH_INCREMENT为步长递增更新。在进行get和set操作时,均由 key.threadLocalHashCode & (len-1) 计算其在数组中的索引。

private final int threadLocalHashCode = nextHashCode();

private static AtomicInteger nextHashCode =
    new AtomicInteger();

private static final int HASH_INCREMENT = 0x61c88647;

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

其它细节这里不做探究。

5、总结

综上我们发现ThreadLocal实现线程隔离其实不难,就是每个线程持有变量副本,各线程操作各自的Entry[] table数组,实现互不干涉。这种设计其实挺巧妙,和锁比较的话,对应是空间换时间的一种方式,所以其并发性能显然更好。

发布了95 篇原创文章 · 获赞 5 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/weixin_43878293/article/details/104599191