ThreadLocal源码分析

本文主要记载本人学习ThreadLocal时对源码的理解,如有不妥或错误,恳请各位指出。


ThreadLocal有哪些主要方法?

方法名 返回值 描述
get() T 返回此线程局部变量的当前线程副本中的值
initialValue() T 返回此线程局部变量的当前线程的“初始值”
remove() void 移除此线程局部变量当前线程的值
set(T value) void 将此线程局部变量的当前线程副本中的值设置为指定值

从get()方法入手:

//返回当前线程的副本值
public T get() {
    //获取当前线程
    Thread t = Thread.currentThread();
    //获取Thread类的成员变量threadLocals
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        //从map中获取key值为当前ThreadLocal对象的引用
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            //获取引用e的value值
            T result = (T)e.value;
            return result;
        }
    }
    //如果map为null或e为null,执行setInitialValue()
    return setInitialValue();
}

ThreadLocalMap类是ThreadLocal类的静态内部类,是一个定制的散列映射,专门用于维护线程局部变量。
Entry类是ThreadLocalMap类的静态内部类,继承WeakReference类,是一个弱引用,其中key为当前ThreadLocal对象,value就是线程局部变量的当前线程副本值。可以通过e.get()方法获取key值和e.value获取副本值。

 static class ThreadLocalMap {

    static class Entry extends WeakReference<ThreadLocal<?>> {
        /** The value associated with this ThreadLocal. */
        Object value;

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

    /*ThreadLocalMap部分成员变量*/

    //初始容量大小
    private static final int INITIAL_CAPACITY = 16;

    //Entry数组,存放当前线程的线程局部变量副本值
    private Entry[] table;

    //table中引用的数量
    private int size = 0;

    //构造函数
    ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
        //初始化table
        table = new Entry[INITIAL_CAPACITY];
        //根据散列值获取存放下标
        int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
        //创建初始value的引用对象
        table[i] = new Entry(firstKey, firstValue);
        size = 1;
        setThreshold(INITIAL_CAPACITY);

    }

    ......

}

继续分析get()方法:

ThreadLocalMap map = getMap(t); 调用getMap()获取当前线程的ThreadLocalMap对象,该方法如下:

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

也就是说我们实际上是获取Thread类中的全局变量引用threadLocals。

public class Thread implements Runnable{
    ...

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

    ...
}

接下来我们看看setInitialValue() 方法:

private T setInitialValue() {
    //调用initialValue()方法,获取初始化value,默认为null,可以通过重写改变初始化值
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        //若map!=null,而e=map.getEntry(this)==null,将初始化值保存到map中
        map.set(this, value);
    else
        //map==null,创建一个新的map,并保存初始化值
        createMap(t, value);
    return value;
}

根据get()方法获取不到value的原因(不存在key=this的引用,或者map为空),执行set(ThreadLocal<?> key, Object value) 方法或者createMap(Thread t, T firstValue) 方法。我们分别来看看这两个方法的实现。

private void set(ThreadLocal<?> key, Object value) {
    //获取存放线程局部变量的引用数组
    Entry[] tab = table;
    //获取数组长度
    int len = tab.length;
    //根据散列值获取遍历的初始下标
    int i = key.threadLocalHashCode & (len-1);
    //从i下标开始遍历,实际上table是一个环形队列
    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {
         //获取key
        ThreadLocal<?> k = e.get();

        if (k == key) {
            e.value = value;
            return;
        }

        if (k == null) {
            //替换key值为null的Entry
            replaceStaleEntry(key, value, i);
            return;
        }
    }
    //向table中插入新的引用
    tab[i] = new Entry(key, value);
    //数量++
    int sz = ++size;
    //清除key==null的引用,防止内存泄漏
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}

从源码可以分析出,set方法主要实现新增key-value的逻辑,首先通过遍历table,如果发现有key=null的引用,取代之,否则新建一个引用。

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

createMap方法就是简单调用ThreadLocalMap的构造方法。

扫描二维码关注公众号,回复: 934606 查看本文章

接下来看看set()方法和remove()方法。

set()

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

set方法逻辑比较简单,和setInitialValue() 类似。若map不为空,插入,否则先新建map。

remove()

public void remove() {
    ThreadLocalMap m = getMap(Thread.currentThread());
    if (m != null)
        m.remove(this);
}

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();
            //清除Entry对象
            expungeStaleEntry(i);
            return;
        }
    }
}

举一个实例

Student实体类

public class Student {
    private int age;
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
} 

ThreadLocalDemo类

public class ThreadLocalDemo implements Runnable{
    //线程局部变量,重写initalValue方法
    private final static ThreadLocal<Student> studentLocal=new ThreadLocal<Student>(){
        @Override
        protected Student initialValue() {
            return new Student();
        }
    };

    @Override
    public void run() {
        accessStudent();
    }

    //访问Student
    private void accessStudent(){
        System.out.println("当前线程"+Thread.currentThread().getName()+" 开始运行");
        Student stu=getStudent();//从线程局部变量中获取Student副本值
        stu.setAge(new Random().nextInt(100));//随机设置年龄
        System.out.println("第一次访问 "+Thread.currentThread().getName()+" age="+stu.getAge());//第一次访问
        stu=getStudent();
        System.out.println("第二次访问 "+Thread.currentThread().getName()+" age="+stu.getAge());//第二次访问
    }

    //从线程局部变量中获取副本值
    private Student getStudent(){
        return studentLocal.get();
    }

    public static void main(String[] args) {
        //测试线程
        new Thread(new ThreadLocalDemo()).start();
        new Thread(new ThreadLocalDemo()).start();
    }
}

运行结果:

当前线程Thread-0 开始运行
当前线程Thread-1 开始运行
第一次访问 Thread-0 age=34
第一次访问 Thread-1 age=12
第二次访问 Thread-0 age=34
第二次访问 Thread-1 age=12

从结果中可以看到,两个线程各自保存了Student的副本值,实现数据的隔离。

我们再举一个例子帮助理解,看下图:
这里写图片描述
Step1:小红向枪支老板拿枪。相当于线程A调用ThreadLocalA的get()方法。

Step2:枪支老板发现小红是新客户,就创建一个属于小红的储物柜,然后拿出一把新枪放到小红的储物柜。相当于调用initialValue()方法初始化副本值,再调用createMap(Thread t, T firstValue)方法创建map,将初始值存入。

Step3:小红再次向枪支老板拿枪,枪支老板发现小红的储物柜已经有一把枪了,就把储物柜的枪给小红。相当于线程A第二次调用ThreadLocalA的get()方法,拿到的副本值和第一次是一样的。

Step4:小红突然觉得弓箭也挺有意思的,就向弓箭老板拿弓,弓箭老板找到了小红的储物柜,但是发现没有弓,就拿了一把新的弓并放到储物柜。相当于线程A调用ThreadLocalB的get()方法,取得map后,再调用 ThreadLocalMap.Entry e = map.getEntry(this);获取副本值的引用对象,但是e的值为空。所以调用setInitialValue()方法,获取初始副本值,然后调用map.set(this, value)保存初始副本值。

如果小明也和小红一样,那么小明也有了一个属于他自己的储物柜,里面放着小明专属的枪和弓。这就意味着小红和小明分别存放了线程局部变量的副本,线程间实现了数据隔离。
关于ThreadLocal的实际应用场景,可以看看这篇文章的应用场景部分。

猜你喜欢

转载自blog.csdn.net/gd_hacker/article/details/79940872