Android开发经验谈Android开发Android技术知识

Android 消息机制之 ThreadLocal 深入源码分析

2020-08-19  本文已影响0人  __Y_Q

接着上一章, 从本章开始源码进行分析 Android 的消息机制.

1. ThreadLocal 是什么.

2. 什么情况下使用 ThreadLocal

下面使用一个例子来简单的使用一下 ThreadLocal.

3. 例

public class MainActivity extends AppCompatActivity {
  final  String TAG = "MainActivity";
  private ThreadLocal<Boolean> mBooleanThreadLocal = new ThreadLocal<>();
  @Override

  protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);
      mBooleanThreadLocal.set(true);
      Log.d(TAG,"[ Thread#main ] mBooleanThreadLocal =" + mBooleanThreadLocal.get());
      new Thread("Thread#1"){
          @Override
          public void run() {
              mBooleanThreadLocal.set(false);
              Log.d(TAG,"[ Thread#1 ] mBooleanThreadLocal =" + mBooleanThreadLocal.get());
          }
      }.start();

      new Thread("Thread#2"){
          @Override
          public void run() {
             // mBooleanThreadLocal.set(false);
              Log.d(TAG,"[ Thread#2 ] mBooleanThreadLocal =" + mBooleanThreadLocal.get());
          }
      }.start();
  }
}

输出结果

D/MainActivity: [ Thread#main ] mBooleanThreadLocal =true
D/MainActivity: [ Thread#1 ] mBooleanThreadLocal =false
D/MainActivity: [ Thread#2 ] mBooleanThreadLocal =null

4. ThreadLocal 内部实现

关键代码如下:

class ThreadLocal<T>{
  
  private final int threadLocalHashCode = nextHashCode();
  
  private static AtomicInteger nextHashCode = new AtomicInteger();
  
  private static int nextHashCode() {
      //自增
      return nextHashCode.getAndAdd(HASH_INCREMENT);
  }
  //分析 1
  public void set(T value) {
      Thread t = Thread.currentThread();
      ThreadLocalMap map = getMap(t);
      if (map != null)
          map.set(this, value);
      else
          createMap(t, value);
  }
 //分析 2
  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();
  }
  ThreadLocalMap getMap(Thread t) {
      return t.threadLocals;
  }

  void createMap(Thread t, T firstValue) {
          t.threadLocals = new ThreadLocalMap(this, firstValue);
  }
}

ThreadLocalMapThreadLocal 的一个静态内部类, 这里先不解释, 下面会有说到.

根据当前所在线程 getMap(t) 获取 ThreadLocalMap 对象, 如果 ThreadLocalMap != null 就将需要保存的值以 <key, value> 的形式保存, keyThreadLocal 实例, value 是传入的参数 value. 如果 ThreadLocalMap == null 就创建一个 ThreadLocalMap 对象, 并将这个对象赋值给当前线程的 threadLocals 属性, 并将值保存.

也是根据当前所在线程获取到 ThreadLocalMap 对象, 然后进行判断. 再根据当前 ThreadLocal 作为 key 进行取值并返回, 如果 ThreadLocalMap == null 那么会调用 setInitialValue() 方法. 后面会说到这个方法.

5. ThreadLocalMap 的内部实现

关键代码 ThreadLocal.java 298行

class ThreadLocal{
  ...
 static class ThreadLocalMap{
    
   static class Entry extends WeakReference<ThreadLocal<?>> {
      Object value;
       Entry(ThreadLocal<?> k, Object v) {
           super(k);
           value = v;
       }
    }
    
    private static final int INITIAL_CAPACITY = 16;
    private Entry[] table;
    
    private void setThreshold(int len) {
       threshold = len * 2 / 3;
    }
    
    ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
       table = new Entry[INITIAL_CAPACITY];
       int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
       table[i] = new Entry(firstKey, firstValue);
       size = 1;
       setThreshold(INITIAL_CAPACITY);
     }
   
    private void set(ThreadLocal<?> key, Object value) {
      //一起贴出来代码会过长, 不方便阅读, 所以下面会单独拿出来看.
    }
   
    private Entry getEntry(ThreadLocal<?> key) {
      //一起贴出来代码会过长, 不方便阅读, 所以下面会单独拿出来看.
    }
  }
}

Entry 以键值对存储, Key 就为 一个 ThreadLocal 对象, value 是一个 Object 对象.

ThreadLocalMapThreadLocal 中的一个静态内部类, 构造方法中定了一个初始大小为 16 的 Entry 数组实例 table, 用于存储 Entry 对象. 那么不难理解 key, value 就是被封装到了 Entry 对象里. 也就是说 ThreadLocalMap 中维护着一张哈希表, 也就是 table 数组, 并设定了一个临界值. setThreshold, 但是当哈希列存储的对象达到容量的 2/3 的时候, 就会扩容.

ThreadLocal 中调用的 get(), set() 其实就是调用 ThreadLocalMap 中的 set(), getEntry() 这两个方法.

5.1 现在开始看 ThreadLocalMap.set()

class ThreadLocal{
  ...
  static class ThreadLocalMap{
    ...
      
        private void set(ThreadLocal<?> key, Object value) {
        Entry[] tab = table;
        int len = tab.length;
        //分析 1
        int index = key.threadLocalHashCode & (len-1);
        for (Entry e = tab[index]; e != null; e = tab[index = nextIndex(index, len)]) {
            ThreadLocal<?> k = e.get();
            if (k == key) {
                e.value = value;
                return;
            }
            if (k == null) { 
                replaceStaleEntry(key, value, index);
                return;
            }
        }
        //分析 2
        tab[index] = new Entry(key, value);
        int sz = ++size;
        if (!cleanSomeSlots(index, sz) && sz >= threshold){
            rehash();          
        }

    }
    //分析 3
    private void rehash() {
        expungeStaleEntries();
        if (size >= threshold - threshold / 4){
            resize();
        }
     } 
  }
 }

在 5 中得知 table 是一个 Entry 数组, 根据数组的长度与当前线程 ThreadLocal 对象的哈希值, 先计算要存储的位置, 然后判断指定的位置是否有数据, 有数据就开始遍历 Entry, 在循环中获取指定位置 Entry 中的 ThreadLocal 是否与传入的 ThreadLocal 相同. 如果相同, 就替换指定位置 Entryvalue 为传入的 value 值. 并返回. 如果没有获取到 ThreadLocal , 说明出现了过期数据, 需要遍历清洗后并在指定位置插入新的数据, 其他的数据后移. 然后返回.

如果 table 数组中不存在指定位置的 Entry 对象, 那么就创建一个 Entry 对象存储. 并且判断 table 数组中存储对象的个数是否超过了临界值, 如果超过了, 就调用 rehash() 方法进行扩容和重新计算所有对象的位置.

先是调用 expungeStaleEntries() 方法删除过期的 Entry 对象 (怎么判断过期呢, 就是 Entry 不为空, 但是 Entry.get() 获取的 key 为空.). 如果清理完后, size >= threshold - threshold / 4 成立 则扩容两倍.

  • 若指定位置的 index 已有数据 Entry 对象, 逐个遍历 Entry
    • indexkey 相同, 折更新 value.
    • indexkeynull, 则调用 replaceStaleEntry () 方法清理过期数据并插入新数据. (从 index 处挨个遍历, 直到找到相同 key 并更新 value 结束, 一直未找到, 则在 index 处放入新的 Entry 对象.) replaceStaleEntry () 遍历时会将 Entry 逐个后移, 也就是说我们 set 进去最新的 Entry 一定会放在 index 处, 方便 get 时直接命中.
  • index 处无数据, 则放入新的 Entry, 税后清理过期数据并判断是否需要 扩容 2 倍.

6. ThreadLocal 总结

上一篇 下一篇

猜你喜欢

热点阅读