美文网首页
ThreadLocal

ThreadLocal

作者: 打杂的_e2c9 | 来源:发表于2020-04-06 15:11 被阅读0次

    ThreadLocal 简介
    ThreadLocal 使用
    ThreadLocal 原理
    InheritableThreadLocal


    ThreadLocal 简介

    ThreadLocal 是线程本地变量,如果创建了一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个副本。

    ThreadLocal 使用

    private static ThreadLocal<String> mThreadLocal = new ThreadLocal<>();
    
        private static void print(String str){
            //打印当前线程本地内存中threadlocal变量的值
            System.out.println(str+":"+mThreadLocal.get());
            // 清除当先线程本地内存中ThreadLoccal 变量的值
            mThreadLocal.remove();
        }
    
        public static void main(String[] args) {
    
            Thread threadOne = new Thread(new Runnable() {
                @Override
                public void run() {
                    mThreadLocal.set("I am One");
                    print("threadOne");
                    System.out.println(mThreadLocal.get());
                }
            });
    
            Thread threadTwo = new Thread(new Runnable() {
                @Override
                public void run() {
                    mThreadLocal.set("I am Two");
                    print("threadTwo");
                    System.out.println(mThreadLocal.get());
                }
            });
    
            threadOne.start();
            threadTwo.start();
        }
    

    ThreadLocal 原理

    • 首先我们看一下ThreadLoca 的set 方法
    // set 操作每个线程都是串行的,不会有线程安全的问题
        public void set(T value) {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            // 当前 thradLocal 之前有设置值,直接设置,否则初始化
            if (map != null)
                map.set(this, value);
            // 初始化ThreadLocalMap
            else
                createMap(t, value);
        }
    
     ThreadLocalMap getMap(Thread t) {
            return t.threadLocals;
        }
    
    void createMap(Thread t, T firstValue) {
            t.threadLocals = new ThreadLocalMap(this, firstValue);
        }
    

    流程:

    • 获取当前线程
    • 获取当前线程中的threadLocals 对象,threadLocals 为ThreadLocalMap对象,ThreadLocalMap是ThreadLocal内部类,存储ThreadLocal 和value 的键值对
    • 如果为不为空则直接将值设置到线程的threadLocals 中
    • 如果为空则初始化线程的threadLocals对象,并设置值
    • get 方法
    public T get() {
            // 因为 threadLocal 属于线程的属性,所以需要先把当前线程拿出来
            Thread t = Thread.currentThread();
            // 从线程中拿到 ThreadLocalMap
            ThreadLocalMap map = getMap(t);
            if (map != null) {
                // 从 map 中拿到 entry,由于 ThreadLocalMap 在 set 时的 hash 冲突的策略不同,导致拿的时候逻辑也不太一样
                ThreadLocalMap.Entry e = map.getEntry(this);
                // 如果不为空,读取当前 ThreadLocal 中保存的值
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    T result = (T)e.value;
                    return result;
                }
            }
            // 否则给当前线程的 ThreadLocal 初始化,并返回初始值 null
            return setInitialValue();
        }
    
    private T setInitialValue() {
            // protected T initialValue() {
            //        return null;
            //    }
            T value = initialValue();
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null)
                map.set(this, value);
            else
                createMap(t, value);
            return value;
        }
    

    get流程:

    • 获取当前线程并获取其他的threadLocals 属性
    • 如果threadLocals不为空,并且获取的此ThreadLocal 对应的Entry不为空,则获取其value值
    • 如果为空,线程的 ThreadLocal 初始化,并返回初始值 null

    ThreadLocal不支持集成性,即在子线程中获取不到父线程中设置的value,下面我们来介绍下可继承的InheritableThreadLocal

    InheritableThreadLocal

    InheritableThreadLocal 是ThreadLocal 的子类,其重写了ThreadLocal 的三个方法

     protected T childValue(T parentValue) {
            return parentValue;
        }
    
    // 获取的是thread 的  inheritableThreadLocals 属性
    ThreadLocalMap getMap(Thread t) {
           return t.inheritableThreadLocals;
        }
    
    // 创建当前线程的inheritableThreadLocals 对象
     void createMap(Thread t, T firstValue) {
            t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
        }
    

    以上getMap 和 createMap保证创建和获取的是Thread 的inheritableThreadLocals 属性,接下来我们看下childValue 在哪使用
    在创建Thread 初始化时代码如下:

    // g 代表线程组,线程组可以对组内的线程进行批量的操作,比如批量的打断 interrupt
        // target 是我们要运行的对象
        // stackSize 可以设置堆栈的大小
        private void init(ThreadGroup g, Runnable target, String name,
                          long stackSize, AccessControlContext acc) {
            ......
            // 当前线程作为父线程
            Thread parent = currentThread();
            .......
            // 当父线程的 inheritableThreadLocals 的值不为空时
            // 会把 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 */
            // 线程 id 自增
            tid = nextThreadID();
        }
    
    // 父线程创建子线程时,就是通过 createInheritedMap 方法传递父线程中的 inheritableThreadLocals
        static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
            return new ThreadLocalMap(parentMap);
        }
    
    private ThreadLocalMap(ThreadLocalMap parentMap) {
                // 得到父线程的 table
                Entry[] parentTable = parentMap.table;
                int len = parentTable.length;
                setThreshold(len);
                table = new Entry[len];
    
                // 从父线程中拷贝值到子线程
                for (int j = 0; j < len; j++) {
                    Entry e = parentTable[j];
                    if (e != null) {
                        @SuppressWarnings("unchecked")
                        // 拿到 key
                        ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
                        if (key != null) {
                            // 拿到 value, childValue 方法子类是可以重新实现的
                            // InheritableThreadLocal 是 ThreadLocal 的子类,InheritableThreadLocal 的 childValue 方法是直接返回 e.value,这是一种浅拷贝。
                            // 如果你想做一些值的拷贝的话,可以重新实现 ThreadLocal.childValue 方法,做值的深度拷贝
                            Object value = key.childValue(e.value);
                            Entry c = new Entry(key, value);
                            // 计算 key 在 table 的索引位置,实际上就是取模运算
                            int h = key.threadLocalHashCode & (len - 1);
                            // 如果 h 索引位置中的值已经存在了,找 i 的下一个索引位置,直到找到为空的位置为止
                            // 这里的冲突是很有可能发生的,比如 len 是 3,那么 6、9 对 3 取模,算出来的索引位置都是 0,于是往下找到为空的位置,这是一种简单的冲突策略吧
                            while (table[h] != null)
                                h = nextIndex(h, len);
                            // 赋值
                            table[h] = c;
                            size++;
                        }
                    }
                }
            }
    

    以上便实现了将父线程中的值拷贝到子线程

    相关文章

      网友评论

          本文标题:ThreadLocal

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