Android如何保证一个线程最多只能有一个Looper?

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都对线程特定数据有支持,这种情况下线程特定对象集可以存储在线程内部。
线程特定对象代理,让客户端能够像访问常规对象一样访问线程特定对象。如果没有代理,客户端必须直接访问线程特定对象集并显示地使用键。相当于ThreadLocal<Looper>。

从概念上讲,可将Thread-Specific Storage的结构视为一个二维矩阵,每个键对应一行,每个线程对应一列。第k行、第t列的矩阵元素为指向相应线程特定对象的指针。线程特定对象代理和线程特定对象集协作,向应用程序线程提供一种访问第k行、第t列对象的安全机制。注意,这个模型只是类比。实际上Thread-Specific Storage模式的实现并不是使用二维矩阵,因为键不一定是相邻整数。
这里写图片描述

参考资料

  1. Thread-local storage
  2. 面向模式的软件架构·卷2:并发和联网对象模式
  • 14
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
Android 中的 Looper 机制是一个消息循环系统,用于管理应用程序中的消息队列。在 Python 中,我们可以使用 `queue` 模块来实现类似的机制。以下是一个使用 `queue` 模块实现的死循环示例: ```python import queue def my_handler(): while True: try: message = my_queue.get(block=True, timeout=1) except queue.Empty: continue # 处理消息 print(f"Received message: {message}") # 将消息放回队列 my_queue.put(message) # 创建消息队列 my_queue = queue.Queue() # 启动消息处理循环 my_handler() ``` 在这个示例中,我们定义了一个 `my_handler()` 函数,它包含一个无限循环,它会从一个队列中获取消息,并对消息进行处理。如果队列为空,循环将等待 1 秒钟,然后重试。在处理完消息后,循环将消息放回队列中,以便其他线程可以处理它。 要使用这个循环,我们需要创建一个队列,并将消息放入队列中。例如: ```python # 将两条消息放入队列中 my_queue.put("Hello") my_queue.put("World") # 等待一段时间 time.sleep(5) # 取出队列中的消息 while not my_queue.empty(): message = my_queue.get() print(f"Got message: {message}") ``` 在这个示例中,我们创建了一个队列,并将两条消息放入队列中。然后,我们等待 5 秒钟,并从队列中取出所有的消息并打印它们。在此过程中,`my_handler()` 函数将一直运行,并处理队列中的消息,直到程序结束。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值