Handler-ThreadLocal分析

news2024/11/20 2:23:49

ThreadLocal 源码分析

在 Android 的 Handler 机制下,ThreadLocal 提供了对不同线程本地变量的副本的保存,并且实现了线程数据的隔离,不同线程的数据不会产生错乱。且在一个线程结束后,其对应在 ThreadLocal 内的数据会被释放,除非有其他地方对这部分数据的引用还存在。

基本介绍

ThreadLocal 提供了线程本地变量保存的功能。线程本地变量的修改由 ThreadLocalset() 实现,读取由 ThreadLocalget() 方法实现。ThreadLocal 实例通常会被定义成 static 字段,这些字段与一个线程的状态关联 ( 例如,用户ID 或 业务ID ) 。

依据 app 启动过程,主线程下 ThreadLocal 的使用进行对应的分析。

Android 主线程 ThreadLocal 的使用

在 Android 主线程中,ThreadLocal 对象被 Looper 所持有,在 Looper 类中被定义 static 字段。

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

在 app 进程开始执行时,先执行 ActivityThread.main() 方法:

// ActivityThread.java

public static void main(String[] args) {
    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
    // ...
    Looper.prepareMainLooper(); // 主线程下,准备main looper。
    // ...
    ActivityThread thread = new ActivityThread();
    thread.attach(false, startSeq);

    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }
    // ...
    Looper.loop(); // looper开始循环

    throw new RuntimeException("Main thread loop unexpectedly exited");
}

主线程中,通过调用 Looper.prepareMainLooper() 创建并初始化 LooperThreadLocal 对象。调用到 Looper.prepareMainLooper() 方法:

// Looper.java

// sThreadLocal.get() 若是在 prepare() 方法调用之前返回,会返回 null。
@UnsupportedAppUsage
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

@UnsupportedAppUsage
private static Looper sMainLooper;  // 这个对象是static的,即它的生命周期跟app一致。

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));  // 设置当前线程与looper关联
}

// 主线程的 main looper 由系统环境创建,因此不需要在 app 程序中调用。
public static void prepareMainLooper() {
    prepare(false);
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();
    }
}

// 返回与当前线程关联的 Looper 对象,若当前线程还没有关联的 Looper 对象,则返回 null。
public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}

在一个线程中,只能创建一个 Looper 对象,否则对应线程就发生crash,包含提示信息 "Only one Looper may be created per thread"。下面时序图是 app 进程中调用 prepareMainLooper() 的调用过程。

在这里插入图片描述

总结起来也就简单几部:

  1. 判断当前线程是否已经有与之关联的 Looper 对象了,若有则抛出运行时错误 ( throw new RuntimeException("Only one Looper may be created per thread");)。
  2. 通过 sThreadLocal.set() 将新建的 Looper 对象与当前线程关联起来。
  3. 判断 sMainLooper 若是不为 null,抛出状态异常 (throw new IllegalStateException("The main Looper has already been prepared."))。
  4. sThreadLocal 中获取与当前线程关联的 Looper 对象,作为主线程 Looper 赋给 sMainLooper

主线程的 looper 创建之后,即 sThreadLocal 中保存有 sMainLooper 引用的对象之后。looper 开始进行 loop() 操作 (后面专门讲 Looper)。

下面 TreadLocal 最常用的方法 set() get() 方法分析。

set()

在 app进程运行时,会创建 main looper,并将其与主线程关联。也就是调用 sThreadLocal.set(new Looper(quitAllowed)) 的过程。

set()方法的定义:

// ThreadLocal.java

private final int threadLocalHashCode = nextHashCode();

// 这个是 static 的,在线程中创建 ThreadLocal 并调用 set() 方法, nextHashCode 的值在每次调用后增长,
// 并被用于计算 Entry 在表中的索引位置值。
private static AtomicInteger nextHashCode = new AtomicInteger();

// hash值增量,每次增加后,与 0x0F(即15) 作且运算,每次计算产生的索引值不同,
// 在计算第16次时,索引值重新开始,与第一次计算的循环值相同。
private static final int HASH_INCREMENT = 0x61c88647;

// 用于增长并非原来的值
private static int nextHashCode() {
    return nextHashCode.getAndAdd(HASH_INCREMENT);
}

// 将线程局部变量的当前线程福报设置给特定值。
public void set(T value) {
    Thread t = Thread.currentThread();  // 获取当前线程
    ThreadLocalMap map = getMap(t);  // 首次调用的时候,getMap(t)方法返回null。
    if (map != null)
        map.set(this, value); // 当 ThreadLocalMap 已经创建,则直接调用 set 方法。
    else
        createMap(t, value);  // 首次调用 getMap(t) 返回Null,因此会执行到这里,创建 ThreadLocalMap 对象。
}

// 获取当前线程的线程本地变量map。
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

// 获取当前线程的本地变量表。
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

// 创建当前线程的 ThreadLocalMap 对象。
void createMap(Thread t, T firstValue) {  // 这里主线程中,传入的 firstValue 是 Looper 对象。
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

// ThreadLocal 内部的 ThreadLocalMap,实际 保存线程本地变量的 实现。
static class ThreadLocalMap {
 	private static final int INITIAL_CAPACITY = 16;
    private Entry[] table;  // 封装了 (ThreadLocal, Looper) 对的结构类。
    private int size = 0;   // 存储有值的 entry 表大小。
    
    static class Entry extends WeakReference<ThreadLocal<?>> {
        Object value;
        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }

    // 创建保存 (firstKey, firstValue) 键值对的 ThreadLocalMaps 对象,其中 firstKey 总是 ThreadLocal。
    ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
        table = new Entry[INITIAL_CAPACITY];
        int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); // 这个方式产生存放 Entry 的索引值。
        table[i] = new Entry(firstKey, firstValue);
        size = 1;
        setThreshold(INITIAL_CAPACITY); // 当 table 数组满时需要扩展表的大小。
    }

}

每次 首次 通过 当前线程对象 调用 getMap(t) 方法尝试获取线程本地变量 map 结果,该方法都会返回 null,因此会调用到 createMap(t, value); 若已经创建了,则调用到 ThreadLocalMap.set(ThreadLocal<?> key, Object value)

// ThreadLocalMap in ThreadLocal.java

// 快速计算下一个索引位置。
private static int nextIndex(int i, int len) {
    return ((i + 1 < len) ? i + 1 : 0);
}

private void set(ThreadLocal<?> key, Object value) { // 主线程中,value 的值是 Looper 对象。

    Entry[] tab = table;
    int len = tab.length;  // 一般就是数组大小的 16,或扩展后的容量大小。
    int i = key.threadLocalHashCode & (len-1);

    // 在 Entry[] 中查找, 若搜索到了相同的 ThreadLocal 的 Entry,直接更新对应的 value。
    for (Entry e = tab[i];
         e != null;
         // 查找下一个搜索位置。
         e = tab[i = nextIndex(i, len)]) {
        ThreadLocal<?> k = e.get();

        if (k == key) { // 搜索到了相同的 ThreadLocal 的 Entry,直接更新对应的 value。
            e.value = value;
            return;
        }

        if (k == null) {  // Entry 的 ThreadLcoal<?> 是 null,替换 Entry 对象。
            // 在当前运行中清除所有过期条目。
            // 主要目的是在插入新元素或者清除过期元素时,清除哈希表中的过期条目,以减少哈希表的大小并提高效率。
            replaceStaleEntry(key, value, i); 
            return;
        }
    }

    tab[i] = new Entry(key, value);
    int sz = ++size;
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}

这段代码是 ThreadLocalset(T) 方法的真正实现,在设置过程中,清理掉 ThreadLocalMap 中的过期条目,过期的判断条件就是 Entry 对象是否被 GC 回收了,即 e.get() == null

get()

get()方法定义:

// 返回当前线程局部变量表的副本。若表中没有值,则返回 setInitialValue() 方法中设置的初始值。
public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

static class ThreadLocalMap {
    // 通过 hashcode 计算 Entry 对象的索引位置。 并获取对应值,若被GC回收了,则调用到 getEntryAfterMiss()。
    private Entry getEntry(ThreadLocal<?> key) {
        int i = key.threadLocalHashCode & (table.length - 1);
        Entry e = table[i];
        if (e != null && e.get() == key)
            return e;
        else
            return getEntryAfterMiss(key, i, e);
    }
    
    // 在直接计算得到的索引位置处无法找到对应的 Entry 时,计算下一个位置,尝试搜索。
    // 若 e.get() == null,表示该条目过期,会对该索引位置 Entry 清理。
    private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
        Entry[] tab = table;
        int len = tab.length;

        while (e != null) {
            ThreadLocal<?> k = e.get();
            if (k == key)
                return e;
            if (k == null)
                expungeStaleEntry(i);
            else
                i = nextIndex(i, len);
            e = tab[i];
        }
        return null;
    }
}

上述的 get() set() 方法是 ThreadLocal 中最常用的两个方法。对于 ThreadLocal 的最常用分析先到这里。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1088679.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Win10 系统中用户环境变量和系统环境变量是什么作用和区别?

环境&#xff1a; Win10专业版 问题描述&#xff1a; Win10 系统中用户环境变量和系统环境变量是什么作用和区别&#xff1f; 解答&#xff1a; 在Windows 10系统中&#xff0c;用户环境变量和系统环境变量是两个不同的环境变量&#xff0c;它们具有不同的作用和区别 1.用…

数字化转型的环境中,MES管理系统的发展趋势如何

近年来&#xff0c;随着数字化技术的飞速发展&#xff0c;数字化转型以及成为企业发展的必然趋势。在这个过程中&#xff0c;制造业作为国民经济的重要支柱&#xff0c;也面临这前所未有的挑战和机遇。数字化转型下&#xff0c;制造业需要什么样的MES管理系统来应对这些挑战和机…

Domain_audit是一款基于渗透测试角度的域审计工具

关于Domain_audit 该工具是PowerView、Impacket、PowerUpSQL、BloodHound、Ldaprelayscan和Crackmapexec的包装器&#xff0c;用于自动执行枚举和在On-Prem Active Directory渗透测试期间执行的大量检查。 检查项目 Invoke-AD CheckAll将按顺序执行以下操作&#xff1a; 收…

竞赛选题 深度学习+python+opencv实现动物识别 - 图像识别

文章目录 0 前言1 课题背景2 实现效果3 卷积神经网络3.1卷积层3.2 池化层3.3 激活函数&#xff1a;3.4 全连接层3.5 使用tensorflow中keras模块实现卷积神经网络 4 inception_v3网络5 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; *…

音视频开发岗位,2023年为何持续增加?如何应聘音视频岗位

随着基础设施的完善&#xff08;光纤入户、wifi覆盖、5G普及&#xff09;&#xff0c;加之2020年疫情的影响&#xff0c;将短视频、直播、视频会议、在线教育、在线医疗瞬间推到了顶峰&#xff0c;人们对音视频的需求和要求也越来越强烈。音视频开发是指利用计算机技术和相关编…

殡葬用品商城小程序的作用是什么

随着互联网电商发展&#xff0c;很多东西由线下被搬到了线上&#xff0c;尤其是围绕生活服务的行业更是线上布局经营增长&#xff0c;而随着消费升级&#xff0c;人们购买商品的方式也由以前单一的线上转为线上。 殡葬用品包括寿盒、寿衣、纸钱等产品虽然几乎每个家庭一辈子也…

人物素材的宝藏:10个网站资源推荐

人物素材是设计中应用最广泛的元素之一。无论是网页设计还是移动终端设计&#xff0c;人物素材的插画设计都比文字信息更容易吸引用户的注意力。作为内容呈现&#xff0c;还可以增加设计的艺术属性。为了节省大家寻找人物素材的时间成本&#xff0c;本文立即为大家整理了10个宝…

在CentOS上安装SQL Server,并通过cpolar内网穿透实现数据库的公网访问

文章目录 前言1. 安装sql server2. 局域网测试连接3. 安装cpolar内网穿透4. 将sqlserver映射到公网5. 公网远程连接6.固定连接公网地址7.使用固定公网地址连接 前言 简单几步实现在Linux centos环境下安装部署sql server数据库&#xff0c;并结合cpolar内网穿透工具&#xff0…

焦炭反应性及反应后强度试验方法

声明 本文是学习GB-T 4000-2017 焦炭反应性及反应后强度试验方法. 而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 1 范围 本标准规定了测定焦炭反应性及反应后强度试验方法的原理、试验仪器、设备和材料、试样的采取 和制备、试验步骤、结果的计算及…

Linux上安装Redis教程

本篇文章是基于CentOS7&#xff0c;安装Redis版本为redis-6.2.6。 一、下载并解压Redis 1、执行下面的命令下载redis&#xff1a; wget https://download.redis.io/releases/redis-6.2.6.tar.gz 2、解压redis&#xff1a; tar xzf redis-6.2.6.tar.gz 3、移动redis目录&a…

洗地机哪款最好用?口碑最好的家用洗地机推荐

洗地机方便快捷的清洁方式&#xff0c;如今融入到我们的日常生活需求中来了&#xff0c;然而&#xff0c;在市面上琳琅满目的洗地机品牌中&#xff0c;究竟哪款洗地机比较好用呢&#xff1f;今天&#xff0c;笔者将向大家推荐四款口碑最好的家用洗地机&#xff0c;让你在挑选时…

安全的Sui Move是Web3大规模采用之路的基石

没有信任&#xff0c;就没有Web3的大规模采用。还有其他重要障碍阻碍了首个十亿用户的到来&#xff0c;包括令人困惑的用户体验、复杂的身份验证模式以及不确定的监管体系&#xff0c;但所有障碍中&#xff0c;要数大多数人对区块链技术持怀疑和不信任态度最严重。 对于许多人…

Chrome 118 版本中的新功能

Google Chrome 的最新版本V118正式版 2023/10/10 发布&#xff0c;以下是新版本中的相关新功能供参考。 本文翻译自 New in Chrome 118&#xff0c;作者&#xff1a; Adriana Jara&#xff0c; 略有删改。 以下是主要内容&#xff1a; 使用scope css规则在组件中指定特定样式。…

洗地机哪个品牌最耐用质量好?2023年最好用的洗地机

随着科技的发展&#xff0c;人们的生活越来越便利&#xff0c;就拿打扫卫生来说&#xff0c;现在越来越多人抛弃扫把、地拖&#xff0c;转而选择让清洁更加轻松的清洁家电&#xff0c;而洗地机就是这样一种让打扫卫生变得简单轻松的家电。近年来洗地机销量剧增&#xff0c;是目…

「UI开发」DevExpress WPF Pivot Grid组件可轻松实现多维数据分析!(一)

DevExpress WPF Pivot Grid组件是一个类似excel的数据透视表&#xff0c;用于多维数据分析和跨选项卡报表生成。众多的布局自定义选项让您完全控制其UI&#xff0c;以用户为中心的功能使其更易于部署。 P.S&#xff1a;DevExpress WPF拥有120个控件和库&#xff0c;将帮助您交…

简单易用,效果卓越的电子期刊制作网站

在日常工作和生活中&#xff0c;我们常常需要制作各种文档和资料&#xff0c;比如电子期刊、宣传册、产品手册等。但有时候&#xff0c;我们会因为排版、设计、编辑等问题而感到烦恼。这时候&#xff0c;一个简单易用、效果卓越的电子期刊制作网站就成为了我们的得力助手&#…

母婴用品会员商城小程序的作用是什么

随着政策放松&#xff0c;母婴行业相比以前迎来了更高的发展空间&#xff0c;由于可以与多个行业连接&#xff0c;因此市场规模也是连年上升&#xff0c;母婴用品是行业重要的分支&#xff0c;近些年从业商家连年增加&#xff0c;但在实际经营中&#xff0c;商家所遇经营痛点也…

字符串查找,替换,合并

字符串查找 字符串查找方法即是查找子串在字符串中的位置或出现的次数 find()&#xff1a;检测某个子串是否包含在这个字符串中&#xff0c;如果在返回这个子串开始的位置下标&#xff0c;否则返回-1 # 字符串序列.find(子串, 开始位置下标, 结束位置下标)mystr "hell…

智慧公厕厂家为城市智慧化建设提供城市卫生升级的力量

城市&#xff0c;作为现代生活的核心&#xff0c;一直致力于提高生活质量&#xff0c;尤其是卫生条件。而在实现这一目标中&#xff0c;智慧公厕厂家扮演着至关重要的角色。但你可能会好奇&#xff0c;究竟"智慧公厕厂家"是干什么的&#xff1f; 提供智能卫生解决方…

星环效果css备忘

<!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta name"viewport" content"widthdevice-width, initial-scale1.0" /><title>卫星围绕球转</title><style>body {…