美文网首页
ThreadLocal源码浅析

ThreadLocal源码浅析

作者: 哪吒不认命 | 来源:发表于2020-05-11 10:25 被阅读0次

    本章内容概要

    • ThreadLocal用法demo
    • ThreadLocal内部类ThreadLocalMap简述
    • ThreadLocal.initialValue()源码
    • ThreadLocal.set()源码
    • ThreadLocal.get()源码
    • 总结

    ThreadLocal用法demo

    先看个demo

    /**
     * Created by corey on 2019/11/17.
     */
    public class ThreadLocalTest {
        
        public static final ThreadLocal<Object> local = new ThreadLocal<Object>(){
            @Override
            protected Object initialValue(){
                System.out.println("调用get方法当threadlocal没有设置值时,调用initialValue方法获取默认值");
                return null;
            }
        };
    
        public static void main(String[] args) {
            // 使用线程池
            ThreadPoolExecutor executors = new ThreadPoolExecutor(10,20,60, TimeUnit.MINUTES,new ArrayBlockingQueue<Runnable>(100));
            // 创建10个线程去执行任务
            for (int i =1;i<11;i++){
                executors.execute(new IntegerTask("我是线程"+i+"哥"));
            }
            executors.shutdown();
        }
        // 自定义任务,获取线程自己的共享变量,然后把拿到的值进行加1操作
        static class IntegerTask implements Runnable{
            private String name;
    
            public IntegerTask(String name) {
                this.name = name;
            }
    
            public void run() {
                for (int i=0;i<10;i++){
                    if(null == local.get()){
                        local.set(0);
                        System.out.println("线程"+name+",IntegerTask:"+local.get());
                    }else{
                        int num = (Integer) local.get();
                        local.set(num+1);
                        System.out.println("线程"+name+",IntegerTask:"+local.get());
                    }
                }
            }
        }
    }
    

    demo中创建了10个线程,每个线程对自己的持有的共享变量循环加1。
    看打印结果:

    调用get方法,threadlocal没有设置值时,调用initialValue方法获取默认值
    线程我是线程1哥,IntegerTask:0
    省略...
    线程我是线程1哥,IntegerTask:9
    调用get方法,threadlocal没有设置值时,调用initialValue方法获取默认值
    线程我是线程2哥,IntegerTask:0
    省略......
    线程我是线程2哥,IntegerTask:9
    调用get方法,threadlocal没有设置值时,调用initialValue方法获取默认值
    线程我是线程3哥,IntegerTask:0
    省略......
    线程我是线程3哥,IntegerTask:9
    调用get方法,threadlocal没有设置值时,调用initialValue方法获取默认值
    .
    省略...
    .
    调用get方法,threadlocal没有设置值时,调用initialValue方法获取默认值
    线程我是线程10哥,IntegerTask:0
    省略...
    线程我是线程10哥,IntegerTask:9
    
    

    从打印结果可知ThreadLocal为每个线程单独保存了自己的共享变量值,在多线程场景中,每个线程都能正确的拿到自己的共享变量进行加1操作。
    ThreadLocal对象是怎么做到为每个线程保存各自的共享变量不受多线程干扰呢?看源码浅析:

    ThreadLocal.initialValue()源码

     // 源码中就定义了一个方法,返回一个null
     protected T initialValue() {
            return null;
     }
    

    当我们调用get()时,如果threadlocal之前没有set过值,就会调用这个方法,一般我们重写此方法来返回符合自己预期的值。

    ThreadLocal内部类ThreadLocalMap简述

    在ThreadLocal类里面定义一个用于存放当前线程实例与线程共享变量的数据结构,它就是ThreadLocalMap。
    它的命名都是以Map结尾,结构当然也是类似Map,只不过它是由ThreadLocal内部实现,与Map集合扯不上关系。
    看一下它的set方法:

    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();
    
                    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是ThreadLocal,value是要设置的对象,然后通过数组长度和key的hash值去数组里面找是否已经存相同的key,如果已经存在就替换value,不存在就放入数组。

    ThreadLocal.set()源码

    1.public void set(T value) {
    2.        Thread t = Thread.currentThread();
    3.        ThreadLocalMap map = getMap(t);
    4.        if (map != null)
    5.            map.set(this, value);
    6.        else
    7.            createMap(t, value);
    8.}
    

    首先是拿到当前的thread实例,然后调用getMap()方法:

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

    这里直接返回Thread对象的threadLocals对象,
    看Thread部分的源码:

    public class Thread implements Runnable {
        private static native void registerNatives();
        static {
            registerNatives();
        }
        private volatile String name;
        private int            priority;
        private Thread         threadQ;
        private long           eetop;
        private boolean     single_step;
        private boolean     daemon = false;
        private boolean     stillborn = false;
        private Runnable target;
        private ThreadGroup group;
        private ClassLoader contextClassLoader;
        private AccessControlContext inheritedAccessControlCont
        private static int threadInitNumber;
        private static synchronized int nextThreadNum() {
            return threadInitNumber++;
        }
        // 这里果真有ThreadLocal.ThreadLocalMap对象
        ThreadLocal.ThreadLocalMap threadLocals = null;
        // 后续代码省略···
    

    线程第一次调用set方法这里返回的必然是null,所有看第7行代码:

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

    这里创建了一个ThreadLocalMap对象,然后赋值给当前线程实例的threadLocals。
    同一个线程第二次调用set方法时,threadLocals就有值了,然后就是走第5行map.set()代码,这个方法在上面一点点已经提到到了。

    ThreadLocal.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();
        }
    

    这段代码很好理解,就是拿到当前线程的threadLocals对象,判断是否已经有值了,有就返回保存的value,没有就是走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;
        }
    

    这里首先就是调用了一开始讲到的initialValue方法,后面的判断没想明白,因为上面get()方法已经判断过了当然是没有值才走这个方法的,这里还重复判断一次,感觉没必要,不过Java原创大神的思路,小弟我还是领悟不透呀。

    总结

    1. ThreadLocal是为Thread提供服务的,每个Thread都有自己的ThreadLocal对象,所以多线程情况下也不会相互影响。
    2. ThreadLocalMap的巧妙设计,数据结构类似Map却与Map毫无关系,重新设计一个内部类是为了符合ThreadLocal的对象存储。所以只要理解设计原理,到哪都可以设计出来最符合自己的东西。

    相关文章

      网友评论

          本文标题:ThreadLocal源码浅析

          本文链接:https://www.haomeiwen.com/subject/wjiynhtx.html