ThreadLocal的介绍及工作原理

一、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 sThreadLocal = new ThreadLocal();
 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的介绍及工作原理)