Java之ThreadLocal的使用及源码解析


前言:

ThreadLocal是一个Java线程中比较重要的类,本文简要分析ThreadLocal的源码以及如何在实战中使用,希望对于学习Java多线程有所帮助~

ThreadLocal是什么

ThreadLocal是一个能创建线程局部变量的类。通过ThreadLocal提供的get和set方法,可以为每一个使用该变量的线程保存一份数据副本,且线程之间是不能相互访问的,从而达到变量在线程间隔离、封闭的效果

使用例子

public static void main(String[] args) throws InterruptedException {

    final ThreadLocal<String> threadLocal = new ThreadLocal<>();
    threadLocal.set("AAA");

    new Thread(()-> {
            threadLocal.set("BBB");
            System.out.println("get in " + Thread.currentThread().getName() + " " + threadLocal.get());
        }).start();
    Thread.sleep(1000);
    System.out.println("get in main thread " + threadLocal.get());
}

执行结果:

get in Thread-0 BBB
get in main thread AAA

首先,在主线程中初始化了ThreadLocal,并且操作的变量是String类型,在主线程中设置该变量为”AAA”,主线程等待1秒钟,同时启动了一个子线程也调用ThreadLocal设置该变量为”BBB”并输出,1秒之后通过get输出主线程的结果,发现子线程设置的值并没有影响主线程中设置的值,即通过ThreadLocal修饰的变量可以实现在各个线程之间互不干扰,相互隔离的效果。

源码解析

初始化
//1
final ThreadLocal<String> threadLocal = new ThreadLocal<>();

threadLocal.set("AAA");
//2
final ThreadLocal<String> threadLocal = new ThreadLocal<String>() {
    
    protected String initialValue() {
        return "AAA";
    }
};

对应的源码:

protected T initialValue() {
    return null;
}

public ThreadLocal() {
}

ThreadLocal的初始化可以有上面1、2两种方式,一种是先初始化然后通过set设置值,一种直接重写initialValue并设置值。既然ThreadLocal可以做到变量的线程封闭,我们有理由猜想是不是ThreadLocal是通过Map<Thread,T>来实现的呢?其中key是当前Thread,value是通过set或者initialValue设置的,看似是这样,但ThreadLocal内部并不是这么实现的,接着往下分析。

set值
public void set(T value) {
    //获取当前线程
    Thread t = Thread.currentThread();
    //根据当前线程获取ThreadLocalMap,注:ThreadLocalMap内部并不是通过map来存储value,而是通过数组存储的
    ThreadLocalMap map = getMap(t);
    if (map != null)
       //不为空,内部直接通过数组设置Entry元素(Entry中包装了ThreadLocal及value,其中key=ThreadLocal,value=传入值value)
        map.set(this, value);
    else
        //为空,则初始化一个ThreadLocalMap,并将ThreadLocal及value包装成Entry放入数组中。
        createMap(t, value);
}

ThreadLocalMap getMap(Thread t) {
    //threadLocals是Thread类中的成员变量
    return t.threadLocals;
}

void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

Thread类:

public class Thread implements Runnable {

     /* ThreadLocal values pertaining to this thread. This map is maintained by the ThreadLocal class. */
     ThreadLocal.ThreadLocalMap threadLocals = null;
   } 

所以set方法首先根据当前线程获取线程中的threadLocals变量(ThreadLocalMap类型),并将ThreadLocal及value包装成Entry放入数组中,因为threadLocals是Thread中的局部变量(存放在栈空间中),所以只有当前线程能访问,其他线程无法访问。这里有个问题:为什么还需要将ThreadLocal作为key传入到ThreadLocalMap呢?因为一个线程中可以初始化多个ThreadLocal,是一对多的关系,所以需要传入ThreadLocal,如果初始化了多个ThreadLocal,根据不同的ThreadLocal可以获得对应的value。那么ThreadLocalMap内部到底是怎么存储的呢?

ThreadLocal静态内部类ThreadLocalMap:

static class ThreadLocalMap {

    //内部类Entry,继承了弱引用WeakReference,使用ThreadLocal作为键值
    static class Entry extends WeakReference<ThreadLocal<?>> {
        Object value;

        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }

    //初始容量 必须是2个倍数
    private static final int INITIAL_CAPACITY = 16;

    //Entry数组,必要时可以扩容,
    private Entry[] table;

    //数组大小
    private int size = 0;

    //初始化ThreadLocalMap,并将ThreadLocal、firstValue封装成Entry并放入Entry数组中
    ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
        table = new Entry[INITIAL_CAPACITY];
        int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
        table[i] = new Entry(firstKey, firstValue);
        size = 1;
        setThreshold(INITIAL_CAPACITY);
    }

    //根据key(ThreadLocal类型)的hash获取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);
    }

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

    //根据key(ThreadLocal类型)设置value值
    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();
            //如果key值在Entry中存在,那么直接覆盖之前的值
            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();
    }

    //移除key对应的value
    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;
            }
        }
    }
}
get值
public T get() {
    //获取当前thread
    Thread t = Thread.currentThread();
    //根据当前线程获取ThreadLocalMap,注:ThreadLocalMap内部并不是通过map来存储value,而是通过数组存储的
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        //根据this(ThreeadLocal)获取数组中对应的Entry,不为空直接取出value
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            T result = (T)e.value;
            return result;
        }
    }
    //如果线程中的ThreadLocalMap为空,则进行初始化
    return setInitialValue();
}

private T setInitialValue() {
    //初始化值 默认是null
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
    return value;
}

总结

  • ThreadLocal存储变量副本实际是保存在每个线程的threadLocals(ThreadLocal.ThreadLocalMap类型)变量中。
  • ThreadLocal包含的对象(指的是ThreadLocal中的T对象)在不同的线程中有不同的副本(实际上也是不同的实例)
  • ThreadLocalMap中的Entry弱引用于ThreadLocal,同时也会回收key为null的Entry,从而避免了Entry无法释放导致内存泄漏

ThreadLocal


文章作者: jackey
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 jackey !
评论
  目录