美文网首页
Looper怎么保证每一个线程的只有一个的!

Looper怎么保证每一个线程的只有一个的!

作者: CharlesCT | 来源:发表于2021-03-18 23:55 被阅读0次

背景

突然被人问到这个问题,心想不是很简单吗!但是他又问你提到ThreadMap,哪ThreadMap怎么解决hash冲突的!这还真把我问住了。

原理

抱着虚心学习,不断遗忘不断再学习的态度,咱们又来看一次源码。
1、Looper是怎么创建的
在Android里面最常见的就是我们主线程的Looper了,主线程的Looper是怎么创建?
我们在从桌面点击一个图标,实际上是通过launchActivity通过startActivity启动Activity的,
会一直走到instrumentation.execStartactivity,然后在里面会跨进程调用ActivityTaskManager的startActivity,然后通过AMS去和Zygote进程进行socket通信,然后会foker一个进程,在这个进程里面反射执行ActivityThrea的mian方法。我们来看看这里有啥
ActiivityThread.java

image.png
我们可以看到很明显的调用了

  //初始化Lopper
   Looper.prepareMainLooper()


   //继续调用了Looper的prepare。
    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

//  我们看到了代码进入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));
    }

在上面的代码中我们都是通过sThreadLocal 来获取Looper的,如果已经存在了Looper就会抛出异常。

   //一个静态变量
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

我们继续查看它的get()方法

//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();
    }

这里由于我们是第一次进来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;
    }

这里还是会去判断这个map,我们去看看这个map到底是啥。

    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

很明显这个map就是一个线程对象的成员变量,一个简单的变量没啥大不了的,不过它的类型为ThreadLocalMap,我们看看这个TreaLocalMap和普通map区别。

    static class ThreadLocalMap {

       //我们可以看到他的Key是一个弱引用!
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

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

        /**
         * The initial capacity -- MUST be a power of two 
         * 这里也很好理解,必须为2的倍数
         */
        private static final int INITIAL_CAPACITY = 16;

        /**
         * The table, resized as necessary. 
         * 这里是容器
         */
        private Entry[] table;

        /**
         * The number of entries in the table.
         */
        private int size = 0;

        /**
         * The next size value at which to resize.
         */
        private int threshold; // Default to 0

        /**
         * Set the resize threshold to maintain at worst a 2/3 load factor.
         */
        private void setThreshold(int len) {
            threshold = len * 2 / 3;
        }

        /**
         * Increment i modulo len.
         */
        private static int nextIndex(int i, int len) {
            return ((i + 1 < len) ? i + 1 : 0);
        }

        /**
         * Decrement i modulo len.
         */
        private static int prevIndex(int i, int len) {
            return ((i - 1 >= 0) ? i - 1 : len - 1);
        }
        //一个构造方法
        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);
        }
}

和普通map区别也不大,里面是一个tab[]用来保存键值对,不过这个Entry的键值对他是一个弱引用,需要值得关注,我们知道GC回收的时候,会对软应用持有的对象进行一次回收。所以这里如果key被回收了,value就是无效的,但是这个map又被线程一直持有,所以会造成内存泄露。
了解这个Map的结构之后我们再来看这个

/////////////////////------ThreadLocal.java-----------------
    private T setInitialValue() {
        T value = initialValue(); // 这里就是一个null值
        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);
    }

这时候就直接调用构造函数,我们知道了会构造一个map,并且执行一次hash,把value设置为null。

到这里我们就知道为什么只能保证一个了,因为Looper的创建只能是通过Looper.prepare()或者时Looper.prepareMain(),就会给调用这个方法的线程创建一个Looper,当你想在创建的时候会判断当前线程对象的变量ThreadLocalMap,是否包含了Looper的Value,如果存在会抛出异常。这里的key就是sTrheadLocal
这时候我们再回答ThreadLocalMap怎么解决hash冲突的?

        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;
                }
            }

可以清楚的看到源码里面是通过线性探测来解决的。不是很麻烦。
一般解决hash冲突有三种方法:线性探测,再hash,以及链表法。

小贴士

线性探测是解决hash冲突的一种方式,比如一个key 的hash值为21,一个key的hash值为11,他们在对数组长度11 (key.threadLocalHashCode & (len-1);)取模的时候,都会定位到1这个位置。
比如先put ke为21这个发现1这个位置没有值,直接就放入了,这时候我们再放入11这个key,他会发现1这个位置已经有值了,他会将下标+1,变为2这个位置,没有值,直接放入。
我们从里面取出元素的时候,比如我们先get 11这个key,直接定位到1这个位置,发现这时候我们不能直接获取value,先要比对equal方法,相等直接返回value,否则需要将下标加1比对下一个位置。

相关文章

网友评论

      本文标题:Looper怎么保证每一个线程的只有一个的!

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