美文网首页
ThreadLocal的介绍及工作原理

ThreadLocal的介绍及工作原理

作者: 空山Echo | 来源:发表于2019-11-18 14:25 被阅读0次

    一、ThreadLocal的作用
    二、ThreadLocal的使用场景(1、2)
    三、ThreadLocal的使用代码示例
    三、ThreadLocal的工作过程(两步)
    四、从ThreadLocal的内部实现分析工作原理(从set、get方法)(为什么ThreadLocal可以在多个线程中互不干扰的储存和修改数据。)

    了解ThreadLocal可以更好的理解Looper的工作原理

    一、ThreadLocal的作用

    可以在指定线程中储存数据,储存以后只有在指定线程中可以获得储存的数据

    二、ThreadLocal的使用场景

    • 当数据的作用域是线程,并且不同线程具有不同的副本

    比如对于Handler来说,Looper的作用域是线程并且不同线程有不同的Looper

    • 复杂逻辑下的对象传递

    比如监听器的传递
    一个线程的任务过于复杂,又需要监听器能贯穿线程执行的过程
    这时候如果不采用ThreadLocal的话有两种方式:1. 将监听器作为参数在函数之间传递 2. 将监听器静态化。 这两种都不如使用ThreadLocal方便可读可扩展

    三、ThreadLocal的使用代码示例

    以Looper.java示例

      static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    
     private static void prepare(boolean quitAllowed) {
            if (sThreadLocal.get() != null) {
                throw new RuntimeException("Only one Looper may be created per thread");
            }
            sThreadLocal.set(new Looper(quitAllowed));
        }
    
      public static @Nullable Looper myLooper() {
            return sThreadLocal.get();
        }
    

    很简单三步:

    1. 创建需要类型的ThreadLocal
    2. 在合适的时机通过ThreadLocal的set方法储存数据
    3. 在合适的时间通过ThreadLocal的get方法获取数据

    四、ThreadLocal的工作过程

    当在不同线程访问同一个ThreadLocal对象,获取的值却不同。过程如下:

    1. 不同线程访问同一个ThreadLocal的get方法时,ThreadLocal内部会从各自的线程中取出一个数组
    2. 然后从数组中根据当前ThreadLocal的索引去查找出对应的value值

    五、从ThreadLocal的set、get方法分析工作原理

    1. ThreadLocal的set方法

     public void set(T value) {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null)
                map.set(this, value);
            else
                createMap(t, value);
        }
    

    那ThreadLocalMap是如何保存ThreadLocal的值的呢?

     private void set(ThreadLocal<?> key, Object value) {
    
                // We don't use a fast path as with get() because it is at
                // least as common to use set() to create new entries as
                // it is to replace existing ones, in which case, a fast
                // path would fail more often than not.
    
                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();
            }
    

    储存数据过程:

    1. 首先调用set方法的当前线程
    2. 然后获得当前线程中储存ThreadLocal数据的数组:ThreadLocalMap(ThreadLocal的内部类对象)
    3. ThreadLocalMap若不为null就通过其set方法对数据进行存储,否则先初始化再存储
    4. 将数据储存在ThreadLocalMapde内部数组table中,使用线性探测的方式确定位置

    所谓线性探测,就是根据初始key的hashcode值确定元素在table数组中的位置,如果发现这个位置上已经有其他key值的元素被占用,则利用固定的算法寻找一定步长的下个位置,依次判断,直至找到能够存放的位置。

    2. ThreadLocal的get方法

    /**
         * Returns the value in the current thread's copy of this
         * thread-local variable.  If the variable has no value for the
         * current thread, it is first initialized to the value returned
         * by an invocation of the {@link #initialValue} method.
         *
         * @return the current thread's value of this thread-local
         */
        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();
        }
    
     /**
         * Variant of set() to establish initialValue. Used instead
         * of set() in case user has overridden the set() method.
         *
         * @return the initial value
         */
        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;
        }
    

    获取数据过程:

    1. 获取当前线程
    2. 获取当前线程下储存ThreadLocal数据的数组map
    3. 当map为空就返回初始值
    4. map不为空就取出其中的数组,并找到以当前ThreadLocal为索引的找到储存的数据

    结语:
    从set、get方法的分析可以看出,他们操作的对象就是当前线程ThreadLocalMap对象的table数组,因此在不同线程访问同一个ThreadLocal的set和get方法,它们的读写操作仅限在各自的线程内部。这也就是为什么ThreadLocal可以在多个线程中互不干扰的储存和修改数据。

    相关文章

      网友评论

          本文标题:ThreadLocal的介绍及工作原理

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