Android怎样保证一个线程最多仅仅能有一个Looper?
2018-02-28 19:58
274 查看
1. 怎样创建Looper?
Looper的构造方法为private,所以不能直接使用其构造方法创建。private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); }
要想在当前线程创建Looper。需使用Looper的prepare方法,Looper.prepare()。
假设如今要我们来实现Looper.prepare()这种方法,我们该怎么做?我们知道,Android中一个线程最多仅仅能有一个Looper,若在已有Looper的线程中调用Looper.prepare()会抛出RuntimeException(“Only one Looper may be created per thread”)。
面对这种需求,我们可能会考虑使用一个HashMap,当中Key为线程ID,Value为与线程关联的Looper,再加上一些同步机制,实现Looper.prepare()这种方法,代码例如以下:
public class Looper { static final HashMap<Long, Looper> looperRegistry = new HashMap<Long, Looper>(); private static void prepare() { synchronized(Looper.class) { long currentThreadId = Thread.currentThread().getId(); Looper l = looperRegistry.get(currentThreadId); if (l != null) throw new RuntimeException("Only one Looper may be created per thread"); looperRegistry.put(currentThreadId, new Looper(true)); } } ... }
上述方法对Looper.class对象进行了加锁。这些加锁开销有可能造成性能瓶颈。
有没有更好的方法实现Looper.prepare()方法?看一看Android的中Looper的源代码。
public class Looper { 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)); } ... }
prepare()方法中调用了ThreadLocal的get和set方法。然而整个过程没有加入同步锁,Looper是怎样实现线程安全的?
2. ThreadLocal
ThreadLocal位于java.lang包中,下面是JDK文档中对该类的描写叙述Implements a thread-local storage, that is, a variable for which each thread has its own value. All threads share the same ThreadLocal object, but each sees a different value when accessing it, and changes made by one thread do not affect the other threads. The implementation supports null values.
大致意思是,ThreadLocal实现了线程本地存储。
全部线程共享同一个ThreadLocal对象,但不同线程仅能訪问与其线程相关联的值。一个线程改动ThreadLocal对象对其它线程没有影响。
ThreadLocal为编写多线程并发程序提供了一个新的思路。例如以下图所看到的,我们能够将ThreadLocal理解为一块存储区,将这一大块存储区切割为多块小的存储区。每一个线程拥有一块属于自己的存储区,那么对自己的存储区操作就不会影响其它线程。对于ThreadLocal<Looper>,则每一小块存储区中就保存了与特定线程关联的Looper。
3. ThreadLocal的内部实现原理
3.1 Thread、ThreadLocal和Values的关系
Thread的成员变量localValues代表了线程特定变量,类型为ThreadLocal.Values。由于线程特定变量可能会有多个,而且类型不确定,所以ThreadLocal.Values有一个table成员变量,类型为Object数组。这个localValues能够理解为二维存储区中与特定线程相关的一列。ThreadLocal类则相当于一个代理。真正操作线程特定存储区table的是其内部类Values。
3.2 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 values(Thread current) { return current.localValues; }
既然与特定线程相关,所以先获取当前线程,然后获取当前线程特定存储,即Thread中的localValues,若localValues为空。则创建一个,最后将value存入values中。
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; } } }
从put方法中,ThreadLocal的reference和值都会存进table,索引分别为index和index+1。
对于Looper这个样例,
table[index] = sThreadLocal.reference;(指向自己的一个弱引用)
table[index + 1] = 与当前线程关联的Looper。
3.3 get方法
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,然后在table中寻找ThreadLocal的reference对象在table中的位置。然后返回下一个位置所存储的对象。即ThreadLocal的值,在Looper这个样例中就是与当前线程关联的Looper对象。
从set和get方法能够看出,其所操作的都是当前线程的localValues中的table数组。所以不同线程调用同一个ThreadLocal对象的set和get方法互不影响,这就是ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。
4. ThreadLocal背后的设计思想Thread-Specific Storage模式
Thread-Specific Storage让多个线程能够使用同样的”逻辑全局“訪问点来获取线程本地的对象。避免了每次訪问对象的锁定开销。4.1 Thread-Specific Storage模式的起源
errno机制被广泛用于一些操作系统平台。errno 是记录系统的最后一次错误代码。对于单线程程序。在全局作用域内实现errno的效果不错,但在多线程操作系统中,多线程并发可能导致一个线程设置的errno值被其它线程错误解读。
当时非常多遗留库和应用程序都是基于单线程编写,为了在不改动既有接口和遗留代码的情况下。解决多线程訪问errno的问题,Thread-Specific Storage模式诞生。
4.2 Thread-Specific Storage模式的整体结构
线程特定对象,相当于Looper。
线程特定对象集包括一组与特定线程相关联的线程特定对象。
每一个线程都有自己的线程特定对象集。
相当于ThreadLocal.Values。
线程特定对象集能够存储在线程内部或外部。Win32、Pthread和Java都对线程特定数据有支持,这种情况下线程特定对象集能够存储在线程内部。
线程特定对象代理,让client能够像訪问常规对象一样訪问线程特定对象。假设没有代理,client必须直接訪问线程特定对象集并显示地使用键。
相当于ThreadLocal<Looper>。
从概念上讲。可将Thread-Specific Storage的结构视为一个二维矩阵,每一个键相应一行。每一个线程相应一列。第k行、第t列的矩阵元素为指向相应线程特定对象的指针。线程特定对象代理和线程特定对象集协作,向应用程序线程提供一种訪问第k行、第t列对象的安全机制。
注意。这个模型仅仅是类比。实际上Thread-Specific Storage模式的实现并非使用二维矩阵,由于键不一定是相邻整数。
參考资料
Thread-local storage面向模式的软件架构·卷2:并发和联网对象模式
相关文章推荐
- Android Handler机制 (一个Thead中可以建立多个Hander,通过msg.target保证MessageQueue中的每个msg交由发送message的handler进行处理 ,但是 每个线程中最多只有一个Looper,肯定也就一个MessageQuque)
- Android如何保证一个线程最多只能有一个Looper?
- Android如何保证一个线程最多只能有一个Looper?
- Android之为什么一个线程只有一个Handler,Looper
- Looper如何和一个线程进行绑定,以及Android中的Can't create handler inside thread that has not called Looper.prepare()
- Android中Looper的实现原理,为什么调用Looper.prepare()就在当前线程关联了一个Looper对象,它是如何实现的。
- Android开发之handler(三)handler真的是重新启动一个线程吗?
- android 停止一个无线循环的线程
- 一个线程运行时发生异常会怎样?
- android为什么不允许新开启一个线程来更新UI,而是用handler来更新界面
- Android中通过使用一个Boolean控制线程结束
- 怎样使一个Android应用不被杀死?(整理)
- Android的线程使用来更新UI----Thread、Handler、Looper、TimerTask等
- Android线程之Looper
- 怎样使用mock object测试一个启动新线程的类
- Android的线程使用来更新UI----Thread、Handler、Looper、TimerTask等
- Android一个线程内连接多个tcp服务端
- Android线程间通信方式:Handler Looper
- Android实战技巧之四十三:终止一个线程引起的
- Android中线程又是一个什么样的存在?