谈谈对ThreadLocal类的理解

源码中对于ThreadLocal类的解释是:
/**
* This class provides thread-local variables.  These variables differ from
* their normal counterparts in that each thread that accesses one (via its
* {@code get} or {@code set} method) has its own, independently initialized
* copy of the variable.  {@code ThreadLocal} instances are typically private
* static fields in classes that wish to associate state with a thread (e.g.,
* a user ID or Transaction ID).
*/
//几个静态常量
private final int threadLocalHashCode = nextHashCode();
private static AtomicInteger nextHashCode = new AtomicInteger();
private static final int HASH_INCREMENT = 0x61c88647;

从上面的官方解释中我们可以得出三点

  • 这个类提供线程局部变量
  • 这些变量与普通的变量不同,因为每个访问一个变量的线程(通过其{@code get}或{@code set}方法)都有自己的、独立初始化的变量副本。
  • {@code ThreadLocal}实例通常是希望将状态与线程关联的类中的私有静态字段。
从表面看,ThreadLocal可以理解为一个Map对象,当前线程作为key,需要存储的对象作为value,可仔细研究过源码后,ThreadLocal本质是通过它的静态内部类ThreadLocalMap来为每个线程维护一个数组table,即Entryp[],当前线程的threadLocalHashCode值作为数组下标。
稍后介绍ThreadLocal的添加(set(T value))、获取(get())、删除(remove())等方法的实现,看完这篇介绍,就会理解ThreadLocal的实现方式,不怕被面试官吊打了。
 
添加实现set(T value)
public void set(T value) {
    //获取当前线程Thread
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}
//获取ThreadLocalMap
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}
//创建ThreadLocalMap 
void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

通过阅读源码,可以知道ThreadLocal的set方法主要分为以下几个步骤

  • 获取当前线程t
  • 通过当前线程使用getMap(t)方法获取当前ThreadLocal的静态内部类ThreadLocalMap 对象map,如果map存在,则调用map.set(this, value)直接保存,否则调用createMap(t, value)方法先创建ThreadLocalMap 对象,在创建的过程中进行保存。
 
删除remove()实现
public void remove() {
    ThreadLocalMap m = getMap(Thread.currentThread());
    if (m != null)
        m.remove(this);
}

ThreadLocal的remove方法比较简单,就是通过当前Thread对象获取到ThreadLocalMap对象,如果不为null的话,调用ThreadLocalMap的remove方法实现。

 
获取get()实现
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();
}
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;
}
protected T initialValue() {
    return null;
}

通过阅读源码,可以知道ThreadLocal的get方法主要分为以下几个步骤

  • 获取当前线程t
  • 通过当前线程使用getMap(t)方法获取当前ThreadLocal的静态内部类的静态内部类ThreadLocalMap 对象map,如果map不等于null,map.getEntry(this)通过获取ThreadLocalMap.Entry对象,返回Entry对象的value值;如果map为null,则调用setInitialValue()方法去设置ThreadLocalMap初始值,最后结果返回null。
 
 
 
简要介绍下ThreadLocalMap
ThreadLocalMap仅用于维护线程本地值的Map,仅作为ThreadLocal的私有静态内部类。
//默认的table容量
private static final int INITIAL_CAPACITY = 16;
//Entry数组,创建ThreadLocalMap的时候会创建一个默认容量的table数组
private Entry[] table;
//当前Entry的长度
private int size = 0;
//下一个要调整大小的值
private int threshold; // Default to 0

上面的四个常量,记录着ThreadLocalMap的状态变化。

 
ThreadLocalMap.set(ThreadLocal<?> key, Object value)
//添加具体实现,该方法为ThreadLocalMap的方法
private void set(ThreadLocal<?> key, Object value) {

    // We don't use a fast path as with get() because it is at
    // least as common to use set() to create new entries as
    // it is to replace existing ones, in which case, a fast
    // path would fail more often than not.
    //获取默认的Entry数组
    Entry[] tab = table;
    int len = tab.length;
    //获取索引
    int i = key.threadLocalHashCode & (len-1);
    //遍历tab,如果存在直接更新
    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;
    //满足条件数组扩容x2
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}

该方法是ThreadLocal的set(T value)的具体实现,通过阅读源码可以知道set(ThreadLocal<?> key, Object value)的实现具体分为以下几个步骤

  • 获取当前table数组tab、tab当前长度、根据当前线程k的threadLocalHashCode值获取索引i
  • 遍历tab如果已经存在Entry对象e,通过e获取当前Threadlocal对象k。
    • 如果k和传入的key相等,则将传入的value赋值给e.value直接更新;
    • 如果k == null,说明threadLocal强引用已经被释放掉,那么就无法不能通过key去访问到相应的value,说明此时存在脏Entry,可以理解为内存泄漏,则调用replaceStaleEntry(key, value, i)去处理,用当前插入的值替换掉这个key为null的“脏”entry。
  • 如果当前tab[i]不存在,则使用new Entry(key, value)创建新的Entry放进tab[i],修改size,插入后调用cleanSomeSlots(int i, int n)再次清除一些key为null的“脏”entry,如果大于阈值就需要进行扩容。
 
ThreadLocalMap.getEntry(ThreadLocal<?> key)
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);
}

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

该方法是ThreadLocal的get()的具体实现,通过阅读源码后可以知道具体实现步骤

  • 根据传入的key获取索引
  • 根据索引获取Entry  e
    • 如果e不为null,并且e.get()得到的Threadlocal等于传入的key,则返回e
    • 如果e为null,则调用getEntryAfterMiss(key, i, e)方法从tab第i个entry往后遍历,找到对应的key == e.get()则返回e;如果e.get()为null,则调用expungeStaleEntry(i)进行清理;如果e.get()不为null也不等于key,重新计算索引得到e;
    • 如果e为null则直接返回null。
 
ThreadLocalMap.remove(ThreadLocal<?> key)
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) {
            e.clear();
            expungeStaleEntry(i);
            return;
        }
    }
}

该方法是ThreadLocal的remove()的具体实现,通过阅读源码后可以知道具体实现步骤

  • 根据传入的key获取索引
  • 遍历tab,如果找到对应的key,则清除当前Entry,再调用expungeStaleEntry(i)进行清理
 
扩展:ThreadLocalMap和Synchronized
  • ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。
  • Synchronized同步机制采用了“以时间换空间”的方式,仅提供一份变量,让不同的线程排队访问;而ThreadLocal采用了“以空间换时间”的方式,每一个线程都提供了一份变量,因此可以同时访问而互不影响。
    • 以时间换空间->即枷锁方式,某个区域代码或变量只有一份节省了内存,但是会形成很多线程等待现象,因此浪费了时间而节省了空间。
    • 以空间换时间->为每一个线程提供一份变量,多开销一些内存,但是呢线程不用等待,可以一起执行而相互之间没有影响。
  • ThreadLocal是解决线程安全问题一个很好的思路,它通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。在很多情况下,ThreadLocal比直接使用synchronized同步机制解决线程安全问题更简单,更方便,且结果程序拥有更高的并发性。

posted @ 2020-05-26 15:23  IT咸鱼圈  阅读(384)  评论(0编辑  收藏  举报