Android 中 ThreadLocal 的作用和实现原理

最近在看 EventBus 源码的时候,发现有用到了 ThreadLocal 这个类。刚好遇到这次作业, 赶紧学习一波。本篇学习参考了《Android开发艺术探索》,故依据的是 Android 5.0 的源码。

1. 不得不说的 Handler

  • Android 的消息机制主要是指 Handler 的运行机制,Handler 的运行需要底层的 MessageQueueLooper 的支撑。
  • MessageQueue 是消息队列, 内部储存了一组消息,以队列的形式对外提供插入和删除的工作。然而,它并不会去处理消息。
  • Looper 是消息循环,它会以无限循环的方式去查找是否有新消息,如果有的话就处理消息。

2. 一个异常

public class MainActivity extends Activity {
    private static final String TAG = MainActivity.class.getSimpleName();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        new LooperThread().start();
    }

    class LooperThread extends Thread {
        public Handler mHandler;

        @Override
        public void run() {

            mHandler = new Handler() {
                @Override
                public void handleMessage(Message msg) {
                    // process incoming messages here
                }
            };

        }
    }
}

这段代码是会抛出异常的,异常信息是这样的:

E/AndroidRuntime: FATAL EXCEPTION: Thread-387
    Process: com.wzc.chapter_4, PID: 7821
    java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
        at android.os.Handler.<init>(Handler.java:208)
        at android.os.Handler.<init>(Handler.java:122)
        at com.wzc.chapter_4.MainActivity$LooperThread$1.<init>(MainActivity.java:25)
        at com.wzc.chapter_4.MainActivity$LooperThread.run(MainActivity.java:25)

查看 Handler 源码,

public Handler(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 that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

在这个构造中, 会先通过 Looper.myLooper() 获取 Looper 对象 mLooper。如果 mLooper 对象为 null, 那么就会抛出上面日志里面的运行时异常。那么,怎样才能有一个 Looper 对象呢?

看一下 Looper 的文档:

Looper 类用于给线程运行一个消息循环。线程默认并没有和它们关联的消息循环;去创建一个 Looper 对象,就要在运行循环的线程里调用 Looper.prepare() 方法,然后再调用 Looper.loop() 就可以让 Looper 对象一直处理消息直到循环结束。
消息循环大多是通过 Handler 类关联的。

修改代码:

public class MainActivity extends Activity {
    private static final String TAG = MainActivity.class.getSimpleName();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        new LooperThread().start();
    }

    class LooperThread extends Thread {
        public Handler mHandler;

        @Override
        public void run() {
            Looper.prepare();
            mHandler = new Handler() {
                @Override
                public void handleMessage(Message msg) {
                    // process incoming messages here
                }
            };
            Looper.loop();
        }
    }
}

运行一下,果然解决了上面的异常。

3. 升级代码

public class MainActivity extends Activity {
    private static final String TAG = MainActivity.class.getSimpleName();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        new LooperThread("Thread1").start();
        new LooperThread("Thread2").start();
    }

    class LooperThread extends Thread {
        public Handler mHandler;

        public LooperThread(String threadName) {
            super(threadName);
        }
        @Override
        public void run() {
            Looper.prepare();
            Log.d(TAG, "run: currThread=" + Thread.currentThread().getName()
                    + ", Looper.myLooper()="+Looper.myLooper());
            mHandler = new Handler() {
                @Override
                public void handleMessage(Message msg) {
                    // process incoming messages here
                }
            };
            Looper.loop();
        }
    }
}

运行一下,查看日志:

D/MainActivity: run: currThread=Thread1, Looper.myLooper()=Looper (Thread1, tid 405) {41c92f18}
D/MainActivity: run: currThread=Thread2, Looper.myLooper()=Looper (Thread2, tid 406) {41c96f38}

可以看到不同的线程里有不同的 Looper 对象。那么,多个线程是如何存储它们对应的 Looper 对象?
先看一下产生 Looper 对象的地方, Looper.prepare()

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

可以看到在 prepare() 方法中, 会 new 一个 Looper 对象,并把 Looper 对象设置给 sThreadLocal 对象。
再看一下获取 Looper 对象的地方,Looper.myLooper()

  public static Looper myLooper() {
        return sThreadLocal.get();
  }

可以看到是从 sThreadLocal 对象中获取 Looper 对象。
sThreadLocal 是什么呢?是定义在 Looper 类中的一个静态成员变量。

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

需要注意的是,这是个静态的 ThreadLocal 变量。也就是说,我们在 Thread1 和 Thread2 这两个线程中访问的是同一个 ThreadLocal 对象,但是它们通过 ThreadLocal 对象 sThreadLocal 获取到的值却是不一样的。这个地方很有意思。
到这里可以知道,ThreadLocal 负责了存储 Looper 对象。然而,这只部分回答了上面的问题。下面进入 ThreadLocal 类中寻找答案。

4. ThreadLocal

public class ThreadLocal<T>

这是一个泛型类,在我们的分析中,T 就是 Looper。再看一下 ThreadLocal 的文档:

实现了一个线程本地的存储,也就是说,为了使每一个线程拥有它自己的值提供一个变量。所有的线程共享同一个 ThreadLocal 对象,但是每个线程只能从 ThreadLocal 对象获取自己存的值,并且一个线程对于存储值的改变不会对其它线程存储的值造成影响。支持存储 null 值。

从文档中看,ThreadLocal 类确实能够回答我们上面的问题。下面从源码中找到依据。不假思索地,我们应该从 ThreadLocal 类的 get()set() 方法中入手。

首先看 ThreadLocal 的 set 方法:

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

在这个方法里,先通过 values() 方法来获取当前线程的 ThreadLocal 数据。看一下 values() 方法:

 Values values(Thread current) {
        return current.localValues;
 }

current.localValuesThread 类中一个成员变量:ThreadLocal.Values localValues; 在这个方法里, ThreadLocal 通过包访问权限直接获取了当前线程的 ThreadLocal.Values localValues 成员变量。
再回到 set() 方法中,如果 valuesnull,那么就会对其进行初始化,初始化的代码比较简单:

  Values initializeValues(Thread current) {
        return current.localValues = new Values();
  }

再调用 Valuesput() 方法,将参数中的 value 进行存储。

 void put(ThreadLocal<?> key, Object value) {
            cleanUp();

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

            for (int index = key.hash & mask;; index = next(index)) {
                Object k = table[index];

                if (k == key.reference) {
                    // Replace existing entry.
                    table[index + 1] = value;
                    return;
                }

                if (k == null) {
                    if (firstTombstone == -1) {
                        // Fill in null slot.
                        table[index] = key.reference;
                        table[index + 1] = value;
                        size++;
                        return;
                    }

                    // Go back and replace first tombstone.
                    table[firstTombstone] = key.reference;
                    table[firstTombstone + 1] = value;
                    tombstones--;
                    size++;
                    return;
                }

                // Remember first tombstone.
                if (firstTombstone == -1 && k == TOMBSTONE) {
                    firstTombstone = index;
                }
            }
        }

接着看 ThreadLocalget() 方法:

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

在这个方法里,先取出当前线程的 Values 对象。如果这个对象不为 null,那么就找到对应的值并返回;如果这个对象为 null,就返回初始值。
来自《Android开发艺术探索》的结论:

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

参考

  • Android 开发艺术探索

猜你喜欢

转载自blog.csdn.net/willway_wang/article/details/81145191