ThreadLocal的工作原理

ThreadLocal是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据,对于其他线程来说则无法获取到数据。

对于Handler来说,它需要获取当前线程的Looper,很显然Looper的作用域就是线程并且不同线程具有不同的Looper,这个时候通过ThreadLocal就可以轻松实现Looper在线程中的存取。

看下ThreadLocal在Handler的使用

我们知道,Handler的工作需要Looper,没有Looper的线程就会报错,那么如何为一个线程创建Looper呢?其实很简单,通过Looper.prepare()即可为当前线程创建一个Looper,接着通过Looper.loop()来开启消息循环,如下所示。

new Thread("Thread#2") {
        @Override
        public void run() {
                Looper.prepare();
                Handler handler = new Handler();
                Looper.loop();
        };
    }.start();

Looper.prepare()的源码

public final class Looper {
 
    private static final String TAG = "Looper";

    // sThreadLocal.get() will return null unless you've called prepare().
    @UnsupportedAppUsage
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    

    public static void prepare() {
        prepare(true);
    }

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

在Thread#2线程ThreadLocal里面放了一个Looper对象,只要下面的代码是在该线程里面执行,那么在任何地方都可以将这个Looper对象拿到。

new Handler做了什么工作?

    public Handler(@Nullable Callback callback, boolean async) {
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }

        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }
  public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

在Looper.myLooper()拿到了Thread#2线程中的Looper对象

下面通过实际的例子来演示ThreadLocal的真正含义。首先定义一个ThreadLocal对象,这里选择Boolean类型的,如下所示。

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

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

 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() {
                Log.d(TAG,"[Thread#2]mBooleanThreadLocal=" + mBooleanThreadLocal.get());
        };
    }.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的get方法,ThreadLocal内部会从各自的线程中取出一个数组,然后再从数组中根据当前ThreadLocal的索引去查找出对应的value值。很显然,不同线程中的数组是不同的,这就是为什么通过ThreadLocal可以在不同的线程中维护一套数据的副本并且彼此互不干扰。参考资料 《Android开发艺术探索》

猜你喜欢

转载自blog.csdn.net/jingerlovexiaojie/article/details/106053823
今日推荐