美文网首页
【Android面试题】Android Framework核心面

【Android面试题】Android Framework核心面

作者: 小城哇哇 | 来源:发表于2023-09-14 17:31 被阅读0次

    ThreadLocal的原理,以及在Looper是如何应用的?(字节跳动、小米)

    这道题想考察什么?

    ThreadLocal 是必须要掌握的,这是因为 Looper 的工作原理,就跟 ThreadLocal 有很大的关系,理解 ThreadLocal 的实现方式有助于我们理解 Looper 的工作原理,这篇文章就从 ThreadLocal 的用法讲起,一步一步带大家理解 ThrealLocal。

    考察的知识点

    1. ThreadLocal的内部运行原理
    2. Looper相关知识

    考生应该如何回答

    ThreadLocal 可以把一个对象保存在指定的线程中,对象保存后,只能在指定线程中获取保存的数据,对于其他线程来说则无法获取到数据。日常开发中 ThreadLocal 使用的地方比较少,但是系统在 Handler 机制中使用了它来保证每一个 Handler 所在的线程中都有一个独立的 Looper 对象,为了更好的理解 Handler 机制。

    ThreadLocal 是什么

    ThreadLocal 是一个关于创建线程局部变量的类。

    其实就是这个变量的作用域是线程,其他线程访问不了。通常我们创建的变量是可以被任何一个线程访问的,而使用 ThreadLocal 创建的变量只能被当前线程访问,其他线程无法访问。

    那么ThreadLocal是如何确保只有当前线程可以访问呢?我们先来分析一下ThreadLocal里面最重要的两个函数,get(),set()两个函数。

    首先看下get()方法中的源码

    public T get() {
        Thread t = Thread.currentThread();  //code 1
        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();
    }
    
    public void set(T value) {
        Thread t = Thread.currentThread();  //code 2
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
    
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
        /**
         * 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;
        }
    

    从code1 和code2 大家应该不难发现,在给ThreadLocal去set值或者get值的时候都会先获取当前线程,然后基于线程去调用getMap(thread),getMap返回的就是线程thread的成员变量threadLocals。所以通过get 和set都执行对于的函数,这样就保证了threadLocal的访问,一定是只能访问或许修改当前线程的值,这就保障了这个变量是线程的局部变量。

    那么接下来ThreadLocalMap又是什么呢?

    ThreadLocalMap是什么

    static class ThreadLocalMap {
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;
    
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
        private Entry[] table;
        //省略部分代码
        }
    

    从源码可以看出来ThreadLocalMap是一个数组,数组里面是继承自弱引用的Entry。弱引用的使用也是为了当出现异常情况,比如死循环的时候内存能得到回收。

    再回过头来看一下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,然后进行存储数据的操作,如果Map为空的话就先创建,再赋值。

    探秘一下map.set()

    private void set(ThreadLocal<?> key, Object value) {
        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();
    }
    

    代码中int i = key.threadLocalHashCode & (len-1)与HashMap中key的hash值方法一样,主要是避免hash值的冲突。再往下走是遍历Map有三种情况:1. 找存在的key有效,然后赋值;2. 找存在的key但是无效,替换掉过期的Entry;3. 没找到相同key值,新建一个Entry然后赋值。

    那么ThreadLocal在Looper中又是如何应用的呢?

    ThreadLocal在Looper中的应用

    找到Looper的源码

    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    
     private static void prepare(boolean quitAllowed) {
            if (sThreadLocal.get() != null) {//code 1
                throw new RuntimeException("Only one Looper may be create per thread");
            }
            sThreadLocal.set(new Looper(quitAllowed));// code 2
        }
    

    Looper与线程的关系是一对一的关系,不同线程之间Looper对象是隔离的,那么Looper是怎么保障这一点的呢?通过上面的代码大家应该不难发现Looper初始化是必须调用prepare函数进行,在调用prepare函数的时候代码会执行到code 1,在code1会先去判断当前线程对于的ThreadLocal中是否存在looper的value,如果存在,那么就抛出异常,这样的执行就保证了一个线程只会设置一次Looper。这个代码的执行流程,就是确保了一个线程只有一个ThreadLocal,一个ThreadLocal就只有一个looper。

    总结

    ThreadLocal是一个创建线程局部变量的类,它的实现机制决定了这个变量的作用域是线程,其他线程访问不了,利用这个机制,可以保障一个线程只有唯一的一个ThreadLocal变量。然后,在looper中通过prepare函数的设计,确保了一个ThreadLocal 只会和一个Looper进行绑定。通过这两个方式确保了一个线程只有一个ThreadLocal变量,一个ThreadLocal变量只有一个Looper,从而形成了一一对应的关系。


    最后

    有需要面试题的朋友可以关注一下哇哇,以上都可以分享!!!

    相关文章

      网友评论

          本文标题:【Android面试题】Android Framework核心面

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