ThreadLocal源码学习

2019-03-09  本文已影响0人  leilifengxingmw

线程本地变量:线程本地变量通常是一个类中的私有静态的成员变量。我们可以在不同的线程中的调用线程本地变量的getset方法,来获取和设置当前线程中线程本地变量的值。在当前线程改变线程本地变量的值,并不会影响其他线程本地变量的值。

上面一段话有点抽象,先举个例子说一下。

public class ThreadLocalTest {

    private static ThreadLocal<String> threadLocal = new ThreadLocal<>();

    public static void main(String[] args) {
        //注释1处,在主线程中设置ThreadLocal保存的变量值
        threadLocal.set("初始名称");
       //注释2处
        System.out.println(Thread.currentThread().getName() + " ," + threadLocal.get());
        new TestThread("线程甲", threadLocal).start();
        new TestThread("线程乙", threadLocal).start();
    }
}

class TestThread extends Thread {

    private ThreadLocal<String> threadLocal;

    public TestThread(String name, ThreadLocal<String> threadLocal) {
        super(name);
        this.threadLocal = threadLocal;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            if (i == 6) {
                //当i==6的时候替换成当前线程名
                threadLocal.set(getName());
            }
            //获取
            System.out.println(Thread.currentThread().getName() + " ," + threadLocal.get() + ",i= " + i);
        }
    }
}

输出如下

main ,初始名称
线程甲 ,null,i= 0
线程甲 ,null,i= 1
线程甲 ,null,i= 2
线程甲 ,null,i= 3
线程甲 ,null,i= 4
线程甲 ,null,i= 5
线程甲 ,线程甲,i= 6
线程甲 ,线程甲,i= 7
线程甲 ,线程甲,i= 8
线程甲 ,线程甲,i= 9
线程乙 ,null,i= 0
线程乙 ,null,i= 1
线程乙 ,null,i= 2
线程乙 ,null,i= 3
线程乙 ,null,i= 4
线程乙 ,null,i= 5
线程乙 ,线程乙,i= 6
线程乙 ,线程乙,i= 7
线程乙 ,线程乙,i= 8
线程乙 ,线程乙,i= 9

在这个例子中,我们创建了一个ThreadLocal变量,内部保存的是一个String类型的变量。

private static ThreadLocal<String> name = new ThreadLocal<>();

在main方法的注释1处,我们在主线程中设置ThreadLocal变量值为初始名称,所以在注释2处输出如下

main ,初始名称

然后我们新建了两个线程,在线程的run方法中首先从0到5threadLocal.get()方法获取的值都是null。
然后从6开始把threadLocal设置为当前线程的名字,然后再调用threadLocal.get()方法输出的就是线程对应的名字了。

源码分析

首先我们看一下ThreadLocal的构造方法

public ThreadLocal() {
    
}

看下ThreadLocal的set(T value)方法相关操作

public void set(T value) {
    Thread t = Thread.currentThread();
    //注释1处
    ThreadLocalMap map = getMap(t);
    //注释2处
    if (map != null)
        map.set(this, value);
    //注释3处
    else
        createMap(t, value);
}

在注释1处,首先获取当前线程的ThreadLocalMap对象。

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

Thread类的threadLocals变量是一个ThreadLocal.ThreadLocalMap对象,用来保存与当前线程相关的ThreadLocal变量。

public class Thread implements Runnable {
    //...
    //用来保存与当前线程相关的ThreadLocal变量
    ThreadLocal.ThreadLocalMap threadLocals = null;
}

ThreadLocal.ThreadLocalMap 是ThreadLocal的一个静态内部类,只适合用来保存线程本地变量。ThreadLocalMap的实体使用弱引用来保存key。

static class ThreadLocalMap {
    
    //初始容量,必须是2的幂
    private static final int INITIAL_CAPACITY = 16;
    //存储entry的表,根据需要调整大小。表的长度必须是2的幂
    private Entry[] table;
    //...
    
    static class Entry extends WeakReference<ThreadLocal<?>> {
        //和ThreadLocal关联的值 
        Object value;
    
        Entry(ThreadLocal<?> k, Object v) {
            //使用弱引用来保存key
            super(k);
            value = v;
        }
    }
}

set(T value)方法的注释2处,如果获取到的map对象不为null,则调用ThreadLocalMap的set(ThreadLocal<?> key, Object value)方法。

ThreadLocalMap的set(ThreadLocal<?> key, Object value)方法

private void set(ThreadLocal<?> key, Object value) {

    Entry[] tab = table;
    int len = tab.length;
    //获取在map中的位置
    int i = key.threadLocalHashCode & (len-1);

    for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
        ThreadLocal<?> k = e.get();
        //更新key对应的value
        if (k == key) {
            e.value = value;
            return;
        }
        //替换指定位置上的键值对
        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }
   //加入到table中
    tab[i] = new Entry(key, value);
    int sz = ++size;
    //是否需要缩减table或者扩容
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}

set(T value)方法的注释3处,如果获取到的map对象为null,则调用createMap方法。

创建一个ThreadLocalMap对象赋值给当前线程的threadLocals变量。同时保存了初始的key和value。

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

ThreadLocalMap的两个参数的构造方法

ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
    table = new Entry[INITIAL_CAPACITY];
    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
    //保存初始的key和value
    table[i] = new Entry(firstKey, firstValue);
    size = 1;
    setThreshold(INITIAL_CAPACITY);
}

接下来看一下ThreadLocal的get方法

public T get() {
    //获取当前线程
    Thread t = Thread.currentThread();
    //获取当前线程的ThreadLocalMap对象map
    ThreadLocalMap map = getMap(t);
    if (map != null) {//如果map存在,返回对应的值
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            T result = (T)e.value;
            return result;
        }
    }
    //map不存在。调用setInitialValue方法
    return setInitialValue();
}

方法内部判断如果线程的threadLocals不为null,就从中取出对应的值并返回,否则返回setInitialValue方法的执行结果。

ThreadLocal的setInitialValue方法

private T setInitialValue() {
    //注释1处,首先调用initialValue方法
    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的initialValue方法默认返回null

protected T initialValue() {
    return null;
}

我们可以重写ThreadLocal的initialValue方法来提供一个默认值,就像这样。

private static ThreadLocal<String> name = new ThreadLocal<String>(){

    @Override
    protected String initialValue() {
       return "hello world";
    }
};

总结:

  1. 实际的通过ThreadLocal创建的副本是存储在每个线程自己的threadLocals中的。
  2. 如果想在调用get方法之前不需要调用set方法就想得到一个默认的值的话,可以重写initialValue()方法。

参考链接:

  1. Java并发编程:深入剖析ThreadLocal
上一篇 下一篇

猜你喜欢

热点阅读