Android中Looper之ThreadLocal

总结:

Looper中定义了一个静态的ThreadLocal<Looper>变量(所有Looper共享同一静态变量),一个静态sMainLooper(主线程Looper)

所有Looper关联了同一个静态的sThreadLocal

所有Thread中都有一个ThreadLocal.Values数组变量


看下设置Looper的过程:

1.线程调用Looper.prepare方法,sThreadLocal设置新的new出来的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));
}


2.首先需要获取当前的线程currentThread,根据当前线程获取线程中的ThreadLocal.Values数组,再将Looper对象添加到数组中

public void set(T value) {
    Thread currentThread = Thread.currentThread();
    Values values = values(currentThread);
    if (values == null) {
        values = initializeValues(currentThread);
    }
    values.put(this, value);
}


获取Looper的过程

1.通过Looper.myLooper()获取,最终通过sThreadLocal.get()方法

/**
 * Return the Looper object associated with the current thread.  Returns
 * null if the calling thread is not associated with a Looper.
 */
public static Looper myLooper() {
    return sThreadLocal.get();
}

2.首先获取当前线程中的ThreadLocal.Values数组,按照算法规则获取数组中的Looper对象

public T get() {
    // Optimized for the fast path.
    Thread currentThread = Thread.currentThread();
    Values values = values(currentThread);
    if (values != null) {
        Object[] table = values.table;
        int index = hash & values.mask;
        if (this.reference == table[index]) {
            return (T) table[index + 1];
        }
    } else {
        values = initializeValues(currentThread);
    }

    return (T) values.getAfterMiss(this);
}

3.数组中获取失败,则进行初始化并添加

/**
 * Gets value for given ThreadLocal after not finding it in the first
 * slot.
 */
Object getAfterMiss(ThreadLocal<?> key) {
    Object[] table = this.table;
    int index = key.hash & mask;

    // If the first slot is empty, the search is over.
    if (table[index] == null) {
        Object value = key.initialValue();

        // If the table is still the same and the slot is still empty...
        if (this.table == table && table[index] == null) {
            table[index] = key.reference;
            table[index + 1] = value;
            size++;

            cleanUp();
            return value;
        }

        // The table changed during initialValue().
        put(key, value);
        return value;
    }

    // Keep track of first tombstone. That's where we want to go back
    // and add an entry if necessary.
    int firstTombstone = -1;

    // Continue search.
    for (index = next(index);; index = next(index)) {
        Object reference = table[index];
        if (reference == key.reference) {
            return table[index + 1];
        }

        // If no entry was found...
        if (reference == null) {
            Object value = key.initialValue();

            // If the table is still the same...
            if (this.table == table) {
                // If we passed a tombstone and that slot still
                // contains a tombstone...
                if (firstTombstone > -1
                        && table[firstTombstone] == TOMBSTONE) {
                    table[firstTombstone] = key.reference;
                    table[firstTombstone + 1] = value;
                    tombstones--;
                    size++;

                    // No need to clean up here. We aren't filling
                    // in a null slot.
                    return value;
                }

                // If this slot is still empty...
                if (table[index] == null) {
                    table[index] = key.reference;
                    table[index + 1] = value;
                    size++;

                    cleanUp();
                    return value;
                }
            }

            // The table changed during initialValue().
            put(key, value);
            return value;
        }

        if (firstTombstone == -1 && reference == TOMBSTONE) {
            // Keep track of this tombstone so we can overwrite it.
            firstTombstone = index;
        }
    }
}



转自:Android的消息机制之ThreadLocal的工作原理


提到消息机制大家应该都不陌生,在日常开发中不可避免地要涉及到这方面的内容。从开发的角度来说,Handler是Android消息机制的上层接口,这使得开发过程中只需要和Handler交互即可。Handler的使用过程很简单,通过它可以轻松地将一个任务切换到Handler所在的线程中去执行。很多人认为Handler的作用是更新UI,这说的的确没错,但是更新UI仅仅是Handler的一个特殊的使用场景,具体来说是这样的:有时候需要在子线程中进行耗时的IO操作,这可能是读取文件或者访问网络等,当耗时操作完成以后可能需要在UI上做一些改变,由于android开发规范的限制,我们并不能在子线程中访问UI控件,否则就会触发程序异常,这个时候通过Handler就可以将更新UI的操作切换到主线程中执行。因此,本质上来说,Handler并不是专门用于更新UI的,它只是常被大家用来更新UI。


       Android的消息机制主要是指Handler的运行机制,Handler的运行需要底层的MessageQueue和Looper的支撑。MessageQueue的中文翻译是消息队列,顾名思义它的内部存储了一组消息,其以队列的形式对外提供插入和删除的工作,虽然叫做消息队列,但是它的内部存储结构并不是真正的队列,而是采用单链表的数据结构来存储消息列表。Looper的中文翻译为循环,在这里可以理解为消息循环,由于MessageQueue只是一个消息的存储单元,它不能去处理消息,而Looper就填补了这个功能,Looper会以无限循环的形式去查找是否有新消息,如果有的话就处理消息,否则就一直等待着。Looper中还有一个特殊的概念,那就是ThreadLocal,ThreadLocal并不是线程,它的作用是可以在每个线程中存储数据。大家知道,Handler创建的时候会采用当前线程的Looper来构造消息循环系统,那么Handler内部如何获取到当前线程的Looper呢?这就要使用ThreadLocal了,ThreadLocal可以在不同的线程之中互不干扰地存储并提供数据,通过ThreadLocal可以轻松获取每个线程的Looper。当然需要注意的是,线程是默认没有Looper的,如果需要使用Handler就必须为线程创建Looper。大家经常提到的主线程,也叫UI线程,它就是ActivityThread,ActivityThread被创建时就会初始化Looper,这也是在主线程中默认可以使用Handler的原因。


       ThreadLocal是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据,对于其它线程来说无法获取到数据。在日常开发中用到ThreadLocal的地方较少,但是在某些特殊的场景下,通过ThreadLocal可以轻松地实现一些看起来很复杂的功能,这一点在Android的源码中也有所体现,比如Looper、ActivityThread以及AMS中都用到了ThreadLocal。具体到ThreadLocal的使用场景,这个不好统一地来描述,一般来说,当某些数据是以线程为作用域并且不同线程具有不同的数据副本的时候,就可以考虑采用ThreadLocal。比如对于Handler来说,它需要获取当前线程的Looper,很显然Looper的作用域就是线程并且不同线程具有不同的Looper,这个时候通过ThreadLocal就可以轻松实现Looper在线程中的存取,如果不采用ThreadLocal,那么系统就必须提供一个全局的哈希表供Handler查找指定线程的Looper,这样一来就必须提供一个类似于LooperManager的类了,但是系统并没有这么做而是选择了ThreadLocal,这就是ThreadLocal的好处。


       ThreadLocal另一个使用场景是复杂逻辑下的对象传递,比如监听器的传递,有些时候一个线程中的任务过于复杂,这可能表现为函数调用栈比较深以及代码入口的多样性,在这种情况下,我们又需要监听器能够贯穿整个线程的执行过程,这个时候可以怎么做呢?其实就可以采用ThreadLocal,采用ThreadLocal可以让监听器作为线程内的全局对象而存在,在线程内部只要通过get方法就可以获取到监听器。而如果不采用ThreadLocal,那么我们能想到的可能是如下两种方法:第一种方法是将监听器通过参数的形式在函数调用栈中进行传递,第二种方法就是将监听器作为静态变量供线程访问。上述这两种方法都是有局限性的。第一种方法的问题时当函数调用栈很深的时候,通过函数参数来传递监听器对象这几乎是不可接受的,这会让程序的设计看起来很糟糕。第二种方法是可以接受的,但是这种状态是不具有可扩充性的,比如如果同时有两个线程在执行,那么就需要提供两个静态的监听器对象,如果有10个线程在并发执行呢?提供10个静态的监听器对象?这显然是不可思议的,而采用ThreadLocal每个监听器对象都在自己的线程内部存储,根据就不会有方法2的这种问题。

       介绍了那么多ThreadLocal的知识,可能还是有点抽象,下面通过实际的例子为大家演示ThreadLocal的真正含义。首先定义一个ThreadLocal对象,这里选择Boolean类型的,如下所示:

private ThreadLocal<Boolean>mBooleanThreadLocal = new ThreadLocal<Boolean>();

       然后分别在主线程、子线程1和子线程2中设置和访问它的值,代码如下所示:

[java]  view plain  copy
  1. mBooleanThreadLocal.set(true);  
  2. Log.d(TAG, "[Thread#main]mBooleanThreadLocal=" + mBooleanThreadLocal.get());  
  3.   
  4. new Thread("Thread#1") {  
  5.     @Override  
  6.     public void run() {  
  7.         mBooleanThreadLocal.set(false);  
  8.         Log.d(TAG, "[Thread#1]mBooleanThreadLocal=" + mBooleanThreadLocal.get());  
  9.     };  
  10. }.start();  
  11.   
  12. new Thread("Thread#2") {  
  13.     @Override  
  14.     public void run() {  
  15.         Log.d(TAG, "[Thread#2]mBooleanThreadLocal=" + mBooleanThreadLocal.get());  
  16.     };  
  17. }.start();  

       在上面的代码中,在主线程中设置mBooleanThreadLocal的值为true,在子线程1中设置mBooleanThreadLocal的值为false,在子线程2中不设置mBooleanThreadLocal的值,然后分别在3个线程中通过get方法去mBooleanThreadLocal的值,根据前面对ThreadLocal的描述,这个时候,主线程中应该是true,子线程1中应该是false,而子线程2中由于没有设置值,所以应该是null,安装并运行程序,日志如下所示:

D/TestActivity(8676):[Thread#main]mBooleanThreadLocal=true

D/TestActivity(8676):[Thread#1]mBooleanThreadLocal=false

D/TestActivity(8676):[Thread#2]mBooleanThreadLocal=null

       从上面日志可以看出,虽然在不同线程中访问的是同一个ThreadLocal对象,但是它们通过ThreadLocal来获取到的值却是不一样的,这就是ThreadLocal的奇妙之处。结合这这个例子然后再看一遍前面对ThreadLocal的两个使用场景的理论分析,大家应该就能比较好地理解ThreadLocal的使用方法了。ThreadLocal之所以有这么奇妙的效果,是因为不同线程访问同一个ThreadLocal的get方法,ThreadLocal内部会从各自的线程中取出一个数组,然后再从数组中根据当前ThreadLocal的索引去查找出对应的value值,很显然,不同线程中的数组是不同的,这就是为什么通过ThreadLocal可以在不同的线程中维护一套数据的副本并且彼此互不干扰。

       对ThreadLocal的使用方法和工作过程做了一个介绍后,下面分析下ThreadLocal的内部实现, ThreadLocal是一个泛型类,它的定义为public class ThreadLocal<T>,只要弄清楚ThreadLocal的get和set方法就可以明白它的工作原理。

       首先看ThreadLocal的set方法,如下所示:

[java]  view plain  copy
  1. public void set(T value) {  
  2.     Thread currentThread = Thread.currentThread();  
  3.     Values values = values(currentThread);  
  4.     if (values == null) {  
  5.         values = initializeValues(currentThread);  
  6.     }  
  7.     values.put(this, value);  
  8. }  

       在上面的set方法中,首先会通过values方法来获取当前线程中的ThreadLocal数据,如果获取呢?其实获取的方式也是很简单的,在Thread类的内容有一个成员专门用于存储线程的ThreadLocal的数据,如下所示:ThreadLocal.Values localValues,因此获取当前线程的ThreadLocal数据就变得异常简单了。如果localValues的值为null,那么就需要对其进行初始化,初始化后再将ThreadLocal的值进行存储。下面看下ThreadLocal的值到底是怎么localValues中进行存储的。在localValues内部有一个数组:private Object[] table,ThreadLocal的值就是存在在这个table数组中,下面看下localValues是如何使用put方法将ThreadLocal的值存储到table数组中的,如下所示:

[java]  view plain  copy
  1. void put(ThreadLocal<?> key, Object value) {  
  2.     cleanUp();  
  3.   
  4.     // Keep track of first tombstone. That's where we want to go back  
  5.     // and add an entry if necessary.  
  6.     int firstTombstone = -1;  
  7.   
  8.     for (int index = key.hash & mask;; index = next(index)) {  
  9.         Object k = table[index];  
  10.   
  11.         if (k == key.reference) {  
  12.             // Replace existing entry.  
  13.             table[index + 1] = value;  
  14.             return;  
  15.         }  
  16.   
  17.         if (k == null) {  
  18.             if (firstTombstone == -1) {  
  19.                 // Fill in null slot.  
  20.                 table[index] = key.reference;  
  21.                 table[index + 1] = value;  
  22.                 size++;  
  23.                 return;  
  24.             }  
  25.   
  26.             // Go back and replace first tombstone.  
  27.             table[firstTombstone] = key.reference;  
  28.             table[firstTombstone + 1] = value;  
  29.             tombstones--;  
  30.             size++;  
  31.             return;  
  32.         }  
  33.   
  34.         // Remember first tombstone.  
  35.         if (firstTombstone == -1 && k == TOMBSTONE) {  
  36.             firstTombstone = index;  
  37.         }  
  38.     }  
  39. }  

       上面的代码实现数据的存储过程,这里不去分析它的具体算法,但是我们可以得出一个存储规则,那就是ThreadLocal的值在table数组中的存储位置总是为ThreadLocal的reference字段所标识的对象的下一个位置,比如ThreadLocal的reference对象在table数组的索引为index,那么ThreadLocal的值在table数组中的索引就是index+1。最终ThreadLocal的值将会被存储在table数组中:table[index + 1] = value。

       上面分析了ThreadLocal的set方法,这里分析下它的get方法,如下所示:

[java]  view plain  copy
  1. public T get() {  
  2.     // Optimized for the fast path.  
  3.     Thread currentThread = Thread.currentThread();  
  4.     Values values = values(currentThread);  
  5.     if (values != null) {  
  6.         Object[] table = values.table;  
  7.         int index = hash & values.mask;  
  8.         if (this.reference == table[index]) {  
  9.             return (T) table[index + 1];  
  10.         }  
  11.     } else {  
  12.         values = initializeValues(currentThread);  
  13.     }  
  14.   
  15.     return (T) values.getAfterMiss(this);  
  16. }  

       可以发现,ThreadLocal的get方法的逻辑也比较清晰,它同样是取出当前线程的localValues对象,如果这个对象为null那么就返回初始值,初始值由ThreadLocal的initialValue方法来描述,默认情况下为null,当然也可以重写这个方法,它的默认实现如下所示:

[java]  view plain  copy
  1. /** 
  2.  * Provides the initial value of this variable for the current thread. 
  3.  * The default implementation returns {@code null}. 
  4.  * 
  5.  * @return the initial value of the variable. 
  6.  */  
  7. protected T initialValue() {  
  8.     return null;  
  9. }  

       如果localValues对象不为null,那就取出它的table数组并找出ThreadLocal的reference对象在table数组中的位置,然后table数组中的下一个位置所存储的数据就是ThreadLocal的值。

       从ThreadLocal的set和get方法可以看出,它们所操作的对象都是当前线程的localValues对象的table数组,因此在不同线程中访问同一个ThreadLocal的set和get方法,它们对ThreadLocal所做的读写操作仅限于各自线程的内部,这就是为什么ThreadLocal可以在多个线程中互不干扰地存储和修改数据,理解ThreadLocal的实现方式有助于理解Looper的工作原理。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值