ThreadLocal源码分析

学习最重要的一点就是回顾总结,今天就总结一下ThreadLocal吧!嘿嘿嘿(猥琐的笑一笑)。

初识ThreadLocal

只记得ThreadLocal位于java.lang包内,被称作线程本地变量。名字也是很生动的。当你创建一个ThreadLocal变量的时候,不同线程操作这个TheadLocal变量(设置或者读取),都只会影响到本线程里面的值。而多个线程对一个普通变量操作的话,就会相互影响,存在并发问题。

简单看一个ThreadLocal的例子

//创建一个ThreadLocal变量
ThreadLocal num = new ThreadLocal<>();

//启动两个线程对这个变量进行修改
new Thread(()->{
    num.set(100);
    try {
        //两个线程都小憩一会,保证两个线程都操作了ThreadLocal变量后,再输出。
        Thread.sleep(100);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName() + " : " + num.get());
},"t1").start();

new Thread(()->{
    num.set(200);
    try {
        Thread.sleep(100);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName() + " : " + num.get());
},"t2").start();

//输出结果
t2 : 200
t1 : 100

可以看到虽然t1,t2两个线程先后修改了ThreadLocal变量,但是之后取出的话还是能够保证,取出的是自己设置的而不是其他线程设置的。

深入了解ThreadLocal

相比于普通变量ThreadLocal的确有一种谜一样的魅力,接下来就开始看一下,它是如何做到多个线程对同一个变量操作但是却不互相影响的呐?

set()方法
public void set(T value) {
    //获取当前线程
    Thread t = Thread.currentThread();
    
    //getMap方法,就一句话  return t.threadLocals 。
    //返回了Thead中的一个成员变量。
    //这个成员变量的类型是ThreadLocal里面定义的一个内部类ThreadLocalMap
    //一个Map的结构
    ThreadLocal.ThreadLocalMap map = getMap(t);
    
    //如果存在的话,则往这个map里面set值
    //其实就是想currentThread的一个ThreadLocalMap类型的Map里面设置值
    //这个Map的key,就是当前这个ThreadLocal对象
    if (map != null)
        map.set(this, value);
    else
        //不存在,就会创建一个ThreadLocalMap,
        //并以当前ThreadLocal对象为key,将k-v放入到ThreadLocalMap中
        //最后把ThreadLocalMap,赋值给currentThread当前线程
        createMap(t, value);
}
get()方法
public T get() {
    //获取当前线程里面的成员变量ThreadLocalMap
    Thread t = Thread.currentThread();
    ThreadLocal.ThreadLocalMap map = getMap(t);
    
    //当Map不为空的时候,尝试以当前的ThreadLocal对象为key
    //从这个TreadLocalMap里面取出Entry对象
    if (map != null) {
        ThreadLocal.ThreadLocalMap.Entry e = map.getEntry(this);
        //当Entry对象不为空,则强转后返回
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

这两个是ThreadLocal中最重要的方法,基本上已经揭开了ThreadLocal的面纱。ThreadLocal更像是一个“工具壳”。通过这个“工具壳”,你可以操作到当前Thread对象的成员变量threadLocals。这个成员变量的类型是ThreadLocal.ThreadLocalMap,他的key就是当前ThreadLocal对象,value就是你要设置的值。需要注意的是这些值本质存储在当前线程对象的一个Map结构中,如果线程不消亡,那么这个变量就会一直存在,所以可能会造成内存溢出

//Thread类里面的定义的两个针对ThreadLocal实现的成员变量
public class Thread implements Runnable{
    //普通的ThreadLocal对象重点操作的Map
    ThreadLocal.ThreadLocalMap threadLocals = null;
    //这个Map是为了让ThreadLocal支持继承
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
}
支持继承的ThreadLocal

ThreadLocal是不支持继承的。这里的继承意思是:在父线程内,通过ThreadLocal设置的值,在子线程内是拿不到的。

//创建一个ThreadLocal,并设值,main方法本身启动一个父线程
ThreadLocal threadLocal = new ThreadLocal<>();
threadLocal.set("mainThread");

new Thread(()->{
   //这里子线程并不能拿到父线程的值
   System.out.println(threadLocal.get());
},"subThread").start();

这就有了后面的支持继承的ThreadLocal - InheritableThreadLocal类
(注意:Thread类中两个成员变量,另一个就是为了支持这个特性的)。

//InheritableThreadLocal 类内容比较简单它继承并重写了ThreadLocal的3个方法
public class InheritableThreadLocal extends ThreadLocal {
    
    protected T childValue(T parentValue) {
        return parentValue;
    }
    
    ThreadLocalMap getMap(Thread t) {
       //上面ThreadLocal的源码,这个方法主要返回了
       //操作当前线程的那个成员变量
       //可以看出Inher..ThreaLocal主要操作的是下面这个
       return t.inheritableThreadLocals;
    }
    
    void createMap(Thread t, T firstValue) {
        //同样的创建的时候也是哈给Thread对象的
        //inheritableThreadLocals赋值
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }
}

综合的来说,InheritableThreadLocal这个工具壳子,重点操作的是Thread对象的inheritableThreadLocals成员变量。但是据此并不能保证它继承的特性。嘿嘿嘿,重点在于Thread类的init方法(ThreadLocal和Thread,他俩是打配合的哈)。

//Thread类的构造函数会调用,init方法,所以重点就是init方法
//调用了几层才到最终的init方法的
public Thread(Runnable runnable){init(...)}

private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
    ...
    Thread parent = currentThread();
    ...
    //重点是这句话,当父线程不为空时,会将父线程中的值复制到子线程的属性中
    if (inheritThreadLocals && parent.inheritableThreadLocals != null)
        this.inheritableThreadLocals =
            ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
    
}

//createInhertedMap直接调用了
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
    return new ThreadLocalMap(parentMap);
}

//最终是在ThreadLocalMap的构造函数中,
//将父线程的inheritableThreadLocals,给copy到子线程的inheritableThreadLocals。

private ThreadLocalMap(ThreadLocalMap parentMap) {
    //ThreadLocalMap 里面有个 Entry数组
    Entry[] parentTable = parentMap.table;
    int len = parentTable.length;
    //长度就是parentTable的length
    //其实也跟hashmap的实现一致,也会保证是2的次方
    setThreshold(len);
    table = new Entry[len];

    for (int j = 0; j < len; j++) {
        Entry e = parentTable[j];
        if (e != null) {
            @SuppressWarnings("unchecked")
            //获取key,这个map的key是一个ThreadLocal对象
            ThreadLocal key = (ThreadLocal) e.get();
            if (key != null) {
                //childValue其实目前没什么用
                //目前的是现实直接又返回了参数
                Object value = key.childValue(e.value);
                //组成一个Entry对象, 并根据key重新计算
                //应该在hash表的那个位置,设置进去。
                Entry c = new Entry(key, value);
                int h = key.threadLocalHashCode & (len - 1);
                while (table[h] != null)
                    h = nextIndex(h, len);
                table[h] = c;
                size++;
            }
        }
    }
}
 
 

总的来说,对于InheritableThreadLocal,它重点操作了Thread对象的inheritableThreadLocals属性,当创建线程时,会判断父线程这个属性是否为空,不为空,则会把这个属性给复制到子线程中,这样子线程就可以访问父线程的本地变量了。

嘿嘿嘿,今天先到这里。

你可能感兴趣的:(ThreadLocal源码分析)