美文网首页Java 杂谈程序员阿里云
ThreadLocal和InheritableThreadLoc

ThreadLocal和InheritableThreadLoc

作者: 良辰美景TT | 来源:发表于2018-08-14 17:09 被阅读39次

      通过ThreadLocal和InheritableThreadLocal,我们能够很方便的设计出线程安全的类。JDK底层是如何做到的呢?ThreadLocal和InheritableThreadLocal有什么区别呢与联系呢?为什么有了ThreadLocal类还需要InheritableThreadLocal类,他们与Thread类是什么关系?带着这些问题我们来分析他们的源码。

    ThreadLocal

      ThreadLocal是为每一个线程创建一个单独的变量副本,每个线程都可以独立地改变自己所拥有的变量副本,而不会影响其他线程所对应的副本。ThreadLocal为多线程环境下变量问题提供了另外一种解决思路。下面来看看ThreadLocal内部是如何做到的。下面分别分析ThreadLocal提供的方法。

    • set方法源码解析
    public class ThreadLocal<T> {
     
      /**
    **传入一个value参数
    **/
        public void set(T value) {
            //首先得到当前线程对象
            Thread t = Thread.currentThread();
            //根据当前线程,得到ThreadLocalMap 引用。
            ThreadLocalMap map = getMap(t);
          //如果map不为空,则把值set到map里
            if (map != null)
                map.set(this, value);
            else
    //map为空,则创建一个map对象,并设置值
                createMap(t, value);
        }
    
    //根据Thread得到 ThreadLocalMap 对象
        ThreadLocalMap getMap(Thread t) {
            return t.threadLocals;
        }
    
    //map为空的话,会新建一个ThreadLocalMap,并将引用交给当前Thread的threadLocals 变量
        void createMap(Thread t, T firstValue) {
            t.threadLocals = new ThreadLocalMap(this, firstValue);
        }
    
    }
    
    从上面的源码上我们可以很容易的看出来,** ThreadLocal只是提供方法方便我们进行编码,而真正存变量的地方是在ThreadLocalMap 这个对象上的**。Thread对象持有ThreadLocalMap 的引用,ThreadLocalMap 以ThreadLocal为key。他们之间的关系如下图所示: ThreadLocal对象关系图
    • get方法源码解析
    public class ThreadLocal<T> {
      
        public T get() {
            //得到当前线程
            Thread t = Thread.currentThread();
        //这里取的是Thread里的ThreadLocalMap引用
            ThreadLocalMap map = getMap(t);
            if (map != null) {
    //从map里取值,key为ThreadLocal对象
                ThreadLocalMap.Entry e = map.getEntry(this);
                if (e != null)
                    return (T)e.value;
            }
    //map为空的话,会调用setInitialValue方法
            return setInitialValue();
        }
    
    //
        private T setInitialValue() {
    //得到一个默认的值,initialValue为protected,留给子类实现,
            T value = initialValue();
    //得到当前线程
            Thread t = Thread.currentThread();
    //再次尝试得到map对象
            ThreadLocalMap map = getMap(t);
            if (map != null)
                map.set(this, value);
            else
    //调用createMap方法
                createMap(t, value);
            return value;
        }
    
        protected T initialValue() {
            return null;
        }
    
    }
    

    get方法无非就是得到当前线程的ThreadLocalMap,如果不为空,则根据ThreadLocal为key进行取值,如果为空,则会调用createMap为Thread线程构造一个ThreadLocalMap对象,并返回initialValue方法的值。

    InheritableThreadLocal

      InheritableThreadLocal用于子线程能够拿到父线程往ThreadLocal里设置的值。使用代码如下:

    public class Test {
    
        public static ThreadLocal<Integer> threadLocal = new InheritableThreadLocal<Integer>();
    
        public static void main(String args[]) {
            threadLocal.set(new Integer(456));
            Thread thread = new MyThread();
            thread.start();
            System.out.println("main = " + threadLocal.get());
        }
    
        static class MyThread extends Thread {
            @Override
            public void run() {
                System.out.println("MyThread = " + threadLocal.get());
            }
        }
    }
    
    输出结果如下图: 输出结果

    如果把上面的InheritableThreadLocal换成ThreadLocal的话,在子线程里的输出将为是空。从上面的代码里也可以看出InheritableThreadLocal是ThreadLocal的子类。下面是InheritableThreadLocal的源码:

    package java.lang;
    import java.lang.ref.*;
    
    public class InheritableThreadLocal<T> extends ThreadLocal<T> {
    
     //这个方法留给子类实现
        protected T childValue(T parentValue) {
            return parentValue;
        }
    
    //重写getMap方法,返回的是Thread的inheritableThreadLocals引用
        ThreadLocalMap getMap(Thread t) {
           return t.inheritableThreadLocals;
        }
    
    //重写createMap方法,构造的ThreadLocalMap会传给Thread的inheritableThreadLocals 变量
        void createMap(Thread t, T firstValue) {
            t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
        }
    }
    
    

    从源码上看,跟ThreadLocal不一样的无非是ThreadLocalMap的引用不一样了,从逻辑上来讲,这并不能做到子线程得到父线程里的值。那秘密在那里呢?通过跟踪Thread的构造方法,你能够发现是在构造Thread对象的时候对父线程的InheritableThreadLocal进行了复制。下面是Thread的部分源码:

    public class Thread implements Runnable {
          //默认人构造方法,会调用init方法进行初使化
          public Thread() {
            init(null, null, "Thread-" + nextThreadNum(), 0);
        }
    
        private void init(ThreadGroup g, Runnable target, String name,
                          long stackSize) {
            init(g, target, name, stackSize, null);
        }
    
    //最终会调用到当前这个方法
        private void init(ThreadGroup g, Runnable target, String name,
                          long stackSize, AccessControlContext acc) {
            if (name == null) {
                throw new NullPointerException("name cannot be null");
            }
    
            this.name = name.toCharArray();
    // parent为当前线程,也就是调用了new Thread();方法的线程
            Thread parent = currentThread();
            SecurityManager security = System.getSecurityManager();
            if (g == null) {
                if (security != null) {
                    g = security.getThreadGroup();
                }
                if (g == null) {
                    g = parent.getThreadGroup();
                }
            }
            g.checkAccess();
            if (security != null) {
                if (isCCLOverridden(getClass())) {
                    security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
                }
            }
    
            g.addUnstarted();
    
            this.group = g;
    //在这里会继承父线程是否为后台线程的属性还有父线程的优先级
            this.daemon = parent.isDaemon();
            this.priority = parent.getPriority();
            if (security == null || isCCLOverridden(parent.getClass()))
                this.contextClassLoader = parent.getContextClassLoader();
            else
                this.contextClassLoader = parent.contextClassLoader;
            this.inheritedAccessControlContext =
                    acc != null ? acc : AccessController.getContext();
            this.target = target;
            setPriority(priority);
    //这里是重点,当父线程的inheritableThreadLocals 不为空的时候,会调用 ThreadLocal.createInheritedMap方法,传入的是父线程的inheritableThreadLocals。原来复制变量的秘密在这里
            if (parent.inheritableThreadLocals != null)
                this.inheritableThreadLocals =
                    ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
            /* Stash the specified stack size in case the VM cares */
            this.stackSize = stackSize;
    
            /* Set thread ID */
            tid = nextThreadID();
        }
    
    }
    

    通过跟踪Thread的构造方法,我们发现只要父线程在构造子线程(调用new Thread())的时候inheritableThreadLocals变量不为空。新生成的子线程会通过ThreadLocal.createInheritedMap方法将父线程inheritableThreadLocals变量有的对象复制到子线程的inheritableThreadLocals变量上。这样就完成了线程间变量的继承与传递。
    ThreadLocal.createInheritedMap方法的源码如下:

    public class ThreadLocal<T> {
    //根据传入的map,构造一个新的ThreadLocalMap
        static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
            return new ThreadLocalMap(parentMap);
        }
    
       static class ThreadLocalMap {
    //这个private的构造方法就是专门给ThreadLocal使用的
              private ThreadLocalMap(ThreadLocalMap parentMap) {
    //ThreadLocalMap还是用Entry数组来存储对象的
                Entry[] parentTable = parentMap.table;
                int len = parentTable.length;
                setThreshold(len);
                table = new Entry[len];
    //这里是复制parentMap数据的逻辑
                for (int j = 0; j < len; j++) {
                    Entry e = parentTable[j];
                    if (e != null) {
                        ThreadLocal key = e.get();
                        if (key != null) {
                            Object value = key.childValue(e.value);
                            Entry c = new Entry(key, value);
                            int h = key.threadLocalHashCode & (len - 1);
                            while (table[h] != null)
                                h = nextIndex(h, len);
                            table[h] = c;
                            size++;
                        }
                    }
                }
            }
      }
    
    }
    

    总结:

    • ThreadLocal和InheritableThreadLocal本质上只是为了方便编码给的工具类,具体存数据是ThreadLocalMap 对象。
    • ThreadLocalMap 存的key对象是ThreadLocal,value就是真正需要存的业务对象。
    • Thread里通过两个变量持用ThreadLocalMap 对象,分别为:threadLocals和inheritableThreadLocals。
    • InheritableThreadLocal之所以能够完成线程间变量的传递,是在new Thread()的时候对inheritableThreadLocals对像里的值进行了复制。
    • 子线程通过继承得到的InheritableThreadLocal里的值与父线程里的InheritableThreadLocal的值具有相同的引用,如果父子线程想实现不影响各自的对象,可以重写InheritableThreadLocal的childValue方法。

    相关文章

      网友评论

        本文标题:ThreadLocal和InheritableThreadLoc

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