美文网首页
ThreadLocal如何使得一个线程只有一个Looper?

ThreadLocal如何使得一个线程只有一个Looper?

作者: hdychi | 来源:发表于2018-07-19 18:02 被阅读0次

一、数据结构

一个线程当中,存有一个ThreadLoacalMap变量:

Thread.java中,有:

 ThreadLocal.ThreadLocalMap threadLocals = null;

ThreadLocalMap,简单来说就是一个类似Map的数据结构,它存储着键值对

ThreadLocalMap中有个Entry类

 /**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

也就是说,在这个哈希map中的键值对Entry继承自ThreadLocal的弱引用,用传入的ThreadLocal的弱引用作为key,用任意对象Object作为值。

即:

未命名文件.png

这个数据结构的主要存储功能实现如下:

static class ThreadLocalMap{
  private Entry[] table;
  private Entry getEntry(ThreadLocal<?> key) {
       int i = key.threadLocalHashCode & (table.length - 1);
       Entry e = table[i];
       if (e != null && e.get() == key)
            return e;
       else
            return getEntryAfterMiss(key, i, e);
  }
  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();
   }
}

用threadlocal键的哈希code按位与上哈希表的长度得到在哈希表中的位置。从而给Entry数组table第i个位置赋值,赋为当前threadlocal为键和要存储的对象为值的Entry。

那么ThreadLocal往ThreadLocal中存储什么类型的值呢?

public class ThreadLocal<T> {
}

泛型T则为往ThreadLocalMap放的值的类型。

二、流程分析

如题目,ThreadLocal如何使得一个线程只有一个Looper?

首先看Looper.java,与ThreadLocal相关的代码:

public final class Looper {
  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));
    }
}

可以看到,它存有一个使用ThreadLocal<Looper> 对象的get方法判断是否哈希map中已经存过Looper,有即抛异常,没有就存入一个new Looper(quitAllowed)对象。

我们来看ThreadLocal中的get方法:

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,如果map为空,返回的就是setInitialValue();

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;
 }
void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
}

就是说,如果当前线程里的ThreadLocalMap为空,就创建一个ThreadLocalMap,这个map里先存上第一个键值对--即当前threadLocal为键,null为值。

setInitialValue()返回的是null。

回到ThreadLocal的get方法:

if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }

如果map部位空,就根据键找j键值对,找到了,就返回键值对中的值。找不到,还是会调用setInitialValue(),存上键值对--即当前threadLocal为键,null为值。

所以呢,由于一个线程跟一个ThreadLocalMap是绑定的,如果这个Map中存有当前Looper里的sThreadLocal为键的键值对,就不会再存粗Looper而会抛异常了,反之,就会存入当前Looper里的sThreadLocal为键,new Looper(quitAllowed)为值的对。

相关文章

网友评论

      本文标题:ThreadLocal如何使得一个线程只有一个Looper?

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