美文网首页
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