【Java】万万没想到,又被问ThreadLocal了

我待ThreadLocal如初恋,ThreadLocal虐我千百遍。但这一次,要彻底搞懂ThreadLocal

回顾一下面试名场面:

面试官:Handler如何做到与线程绑定的?

我:每个Handler只有一个相关联的Looper,线程绑定关键点正是Looper中和其内部的ThreadLocal类型的变量sThreadLocal。通过ThreadLocal完成了Looper和线程的绑定。

问:ThreadLocal原理是什么?

我内心:好像有个map,有个泛型,死活想不起啊。。。

10S后

我内心:我嘞个擦,ThreadLocal到底是什么鬼啊,为什么会有这么奇!怪!的!类!啊

打开源码开干吧

目录

一.ThreadLocal源码注释及使用方法

二.关键方法get和set

1.set

2.get

三.看看Looper怎么做的

三.总结


一.ThreadLocal源码注释及使用方法

看看第一段注释:

This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).

此类提供线程局部变量。这些变量与正常变量的不同之处在于,访问一个变量的每个线程(通过其 get 或 set 方法)都有自己的、独立初始化的变量副本。ThreadLocal 实例通常是类中的私有静态字段,并将其状态与线程相关联(例如,用户 ID 或事务 ID)。

大概意思就是:ThreadLocal这个变量,顾名思义,就是用来管理(设置和获取)线程的局部变量的一个工具类。你要用这个ThreadLocal的时候,最好就是把这个定义成一个私有的静态变量。在不同的线程中,调用这个静态变量的set和get方法,都能设置或者获取属于自己的线程局部变量。当然这个变量的类型也是有限制的,就是ThreadLocal的泛型类型。

这里有个关键点:ThreadLocal通常定义成静态变量。

官方怕你不会用,注释第二段就给了一个例子

  import java.util.concurrent.atomic.AtomicInteger;public class ThreadId {// 原子整型,表示下一个要分配给下一个线程的id(这个线程ID是我们自己定义的,跟线程自身id不同)// Atomic integer containing the next thread ID to be assignedprivate static final AtomicInteger nextId = new AtomicInteger(0);// 重点:ThreadLocal变量,包含我们给每个线程定义的id(这里的ThreadLocal对象是个私有静态变量)// Thread local variable containing each thread's IDprivate static final ThreadLocal<Integer> threadId =new ThreadLocal<Integer>() {@Override protected Integer initialValue() {// 初始化方法,当调用get方法第一次获取线程id的时候会初始化。return nextId.getAndIncrement();}};// 返回当前线程的唯一id。如果没有的话就给他分配一个。// Returns the current thread's unique ID, assigning it if necessarypublic static int get() {return threadId.get();}}

 

上面例子里的ThreadId用起来是啥效果呢?我写个demo跑一下

 

 

val thread1 = Thread {Log.i("threadLocal", "thread1 :" + ThreadId.get())Log.i("threadLocal", "thread1 :" + ThreadId.get())
}
val thread2 = Thread {Log.i("threadLocal", "thread2 :" + ThreadId.get())Log.i("threadLocal", "thread2 :" + ThreadId.get())
}thread1.start()
thread2.start()//  打印结果如下
//  thread1 :0
//  thread2 :1
//  thread2 :1
//  thread1 :0

通过ThreadId这个工具类,可以很方便的去获取到自定义的线程id。

代码注释中会高频地出现一下两个名词:

ThreadLocal实例(ThreadLocal instances)

线程局部变量副本(copy of a thread-local variable)

这个局部变量副本很容易让人迷惑和误解。为了方便理解,我把ThreadLocal实例(也就是定义的那个静态变量)理解为 “ThreadLocal工具对象” ,把线程局部变量副本就叫做 “线程局部变量” 。线程中调用ThreadLocal工具对象可以方便地存取指定类型的变量。

第三段注释:

Each thread holds an implicit reference to its copy of a thread-local variable as long as the thread is alive and the ThreadLocal instance is accessible; after a thread goes away, all of its copies of thread-local instances are subject to garbage collection (unless other references to these copies exist).

只要线程处于活动状态并且 ThreadLocal 实例可访问,每个线程都对他的线程局部变量副本持有隐式引用。当线程销毁后,线程局部变量实例的所有副本都将可能被垃圾回收掉(除非存在对这些副本的其他引用)。

大概意思就是:

只要线程还活着,ThreadLocal对象还能访问,每个线程对他的线程局部变量就有引用。线程销毁之后,线程局部变量就会成为垃圾回收的对象。

二.关键方法get和set

ThreadLocal中有get和set方法,分别用来获取和设置当前线程的“线程局部变量”。

1.set

先看看set方法和其相关的方法,这三个方法都是定义在ThreadLocal中:

 

 

public void set(T value) {// 获取当前线程Thread t = Thread.currentThread();// 获取线程的成员变量threadLocals,类型为ThreadLocalMapThreadLocalMap map = getMap(t);if (map != null) {// 如果map不为null,添加键值对 “ThreadLocal工具对象:value”map.set(this, value);} else {// 如果为null,给map初始化并添加键值对createMap(t, value);}
}/**
* 获取线程的threadLocals成员变量,类型为ThreadLocal.ThreadLocalMap
*/
ThreadLocalMap getMap(Thread t) {return t.threadLocals;
}/**
* 为线程t创建ThreadLocalMap对象,构造函数参数为ThreadLocal工具对象,以及初始值
*/
void createMap(Thread t, T firstValue) {t.threadLocals = new ThreadLocalMap(this, firstValue);
}

说明一下,这里出现了一个新的类ThreadLocalMap,它是定义在ThreadLocal中的静态内部类,内部是一个哈希表,保存键值对,只不过保存键值对的key必须是ThreadLocal类型的,值就是上面说的“线程局部变量”。

ThreadLocalMap便是保存“ThreadLocal:Object”的哈希表。

那我又问了,Thread的ThreadLocalMap的成员变量是什么样的,就下面这样的,不仅有还有两个,我们用的是第一个。

public class Thread implements Runnable {// ... 省略/* ThreadLocal values pertaining to this thread. This map is maintained* by the ThreadLocal class. */ThreadLocal.ThreadLocalMap threadLocals = null;/** InheritableThreadLocal values pertaining to this thread. This map is* maintained by the InheritableThreadLocal class.*/ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;// ... 省略
}

 

到这里要总结一下了,为了方便理解,用非常不专业的uml图,看看Thread、ThreadLocal、ThreadLocalMap他们三个的引用关系:

ThreadLocal以自身为key,从调用线程中获取value。

关键点来了:ThreadLocal乃真谋士,以身为饵(key),引天下入局。

2.get

继续看get方法

    public T get() {// 获取当前线程Thread t = Thread.currentThread();// 获取线程的成员变量threadLocals,类型为ThreadLocalMapThreadLocalMap map = getMap(t);if (map != null) {// 获取当前ThreadLocal工具类为key对应的valueThreadLocalMap.Entry e = map.getEntry(this);if (e != null) {@SuppressWarnings("unchecked")T result = (T)e.value;return result;}}// map为null则返回初始值,默认初始值为nullreturn setInitialValue();}private T setInitialValue() {T value = initialValue();Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null) {map.set(this, value);} else {createMap(t, value);}if (this instanceof TerminatingThreadLocal) {TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);}return value;}protected T initialValue() {return null;}

 

set看懂的话,get就很容易理解,唯一要注意的是获取初始值initialValue这个方法可能在实际使用时可能被重写。

三.看看Looper怎么做的

Looper中用到ThreadLocal的地方全都在这里了:

public final 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));}public static @Nullable Looper myLooper() {return sThreadLocal.get();}// 此处省略
}

 

sThreadLocal为静态变量。

prepare方法中为当前线程设置Looper。

三.总结

这个ThreadLocal写法确实剑走偏锋,常规思维容易记混乱,不过记住两个关键点就可以把整个逻辑回想起来:

1.ThreadLocal通常定义成静态变量

2.ThreadLocal以自身为key,从当前Thread的map中获取value

好了,至此分享结束。

此刻我的心情是:面试官快来问我ThreadLocal的问题吧,我已经迫不及待地想回答了。

 

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

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

相关文章

如何开始深度学习,从实践开始

将“如何开始深度学习”这个问题喂给ChatGPT和文心一言&#xff0c;会给出很有专业水准的答案&#xff0c;比如&#xff1a; 要开始深度学习&#xff0c;你可以遵循以下步骤&#xff1a; 学习Python编程语言的基础知识&#xff0c;因为它在深度学习框架中经常被使用。 熟悉线性…

自然人如何代开发票

1&#xff1a;登录国家税务总局深圳市电子税务局 地址&#xff1a;国家税务总局深圳市电子税务局 2&#xff1a;个人所得税APP 扫描登录 或 身份证登录 3&#xff1a;选择 自然人代开增值税电子普通发票 4&#xff1a;申请代开 5&#xff1a;人脸识别 6&#xff1a;画框的…

【每日一题】LeetCode——链表的中间结点

&#x1f4da;博客主页&#xff1a;爱敲代码的小杨. ✨专栏&#xff1a;《Java SE语法》 | 《数据结构与算法》 | 《C生万物》 ❤️感谢大家点赞&#x1f44d;&#x1f3fb;收藏⭐评论✍&#x1f3fb;&#xff0c;您的三连就是我持续更新的动力❤️ &#x1f64f;小杨水平有…

【Python4Delphi】学习笔记(一):介绍篇

一、前言&#xff1a; 1. python语言简介&#xff1a; 众所周知&#xff0c;python是目前非常流行的编程语言之一&#xff0c;自20世纪90年代初Python语言诞生至今&#xff0c;它已被逐渐广泛应用于系统管理任务的处理和Web编程。 由于Python语言的简洁性、易读性以及可扩展性…

苏宁易购移动端首页(rem布局)

技术选型 方案∶采取单独制作移动页面方案技术:布局采取rem适配布局( less rem &#xff0b;媒体查询)设计图:设计图采用750px设计尺寸 设置视口标签以及引入初始化样式 <meta name"viewport" content"widthdevice-width, initial-scale1.0, user-scalable…

Qt网络编程-写一个简单的网络调试助手

环境 Windows&#xff1a;Qt5.15.2&#xff08;VS2022&#xff09; Linux&#xff1a;Qt5.12.12&#xff08;gcc) 源代码 TCP服务器 头文件&#xff1a; #ifndef TCPSERVERWIDGET_H #define TCPSERVERWIDGET_H #include <QWidget> namespace Ui { class TCPServerW…

注意啦,MySQL8.0最新版是没有utf8选项,但是有utf8mb3和utf8mb4选项

今天在安装完MySQL最新版&#xff08;8.0.36&#xff09;&#xff0c;然后用navicat连接数据&#xff0c;创建数据库的时候&#xff0c;发现: MySQL最新版是没有utf8选项&#xff0c;但是有utf8mb3和utf8mb4选项 然后就只能卸载掉最新版&#xff0c;安装了8.0.28. &#xff08…

一文学会Axios的使用

异步请求 同步发送请求过程如下 浏览器页面在发送请求给服务器&#xff0c;在服务器处理请求的过程中&#xff0c;浏览器页面不能做其他的操作。只能等到服务器响应结束后才能&#xff0c;浏览器页面才能继续做其他的操作。 异步发送请求过程如下浏览器页面发送请求给服务器&…

JavaScript 跨窗口通信(Cross-Window Communication)

&#x1f9d1;‍&#x1f393; 个人主页&#xff1a;《爱蹦跶的大A阿》 &#x1f525;当前正在更新专栏&#xff1a;《VUE》 、《JavaScript保姆级教程》、《krpano》、《krpano中文文档》 ​ ​ ✨ 前言 在现代 Web 开发中&#xff0c;跨窗口通信是一种常见需求。它允许在…

服务器和云计算之间有什么关系?

云计算与服务器之间的关系是密切而复杂的。首先&#xff0c;我们需要明确一点&#xff0c;云计算并不是一种全新的技术&#xff0c;而是对现有技术的一种整合和改进。在这个基础上&#xff0c;我们可以更好地理解云计算与服务器之间的关系。 服务器是云计算的重要组成部分之一…

如何在Linux上部署1Panel运维管理面板并实现无公网ip远程访问

文章目录 前言1. Linux 安装1Panel2. 安装cpolar内网穿透3. 配置1Panel公网访问地址4. 公网远程访问1Panel管理界面5. 固定1Panel公网地址 前言 1Panel 是一个现代化、开源的 Linux 服务器运维管理面板。高效管理,通过 Web 端轻松管理 Linux 服务器&#xff0c;包括主机监控、…

Python中使用opencv-python库进行颜色检测

Python中使用opencv-python库进行颜色检测 之前写过一篇VC中使用OpenCV进行颜色检测的博文&#xff0c;当然使用opencv-python库也可以实现。 在Python中使用opencv-python库进行颜色检测非常简单&#xff0c;首选读取一张彩色图像&#xff0c;并调用函数imgHSV cv2.cvtColor…

华为 Huawei 交换机 黑洞MAC地址的作用和配置示例

黑洞mac作用&#xff1a;某交换机上配置某个PC的mac地址为黑洞mac&#xff0c;那么这台PC发出来的包都会被交换机丢弃&#xff0c;不会被转发到网络中。 组网需求&#xff1a; 如 图 2-13 所示&#xff0c;交换机 Switch 收到一个非法用户的访问&#xff0c;非法用户的 MAC 地址…

C++新版本特性

目录: 前言 C11的常用新特性 auto类型推导&#xff1a; auto的限制&#xff1a; auto的应用&#xff1a; decltype类型推导&#xff1a; decltype的实际应用&#xff1a; 使用using 定义别名&#xff1a; 支持函数模板的默认模板参数 : tuple元组&#xff1a; 列表初…

每日OJ题_位运算①_位运算解题方法+3道OJ

目录 位运算算法原理 ①力扣191. 位1的个数 解析代码 ②力扣338. 比特位计数 解析代码 ③力扣461. 汉明距离 解析代码 位运算算法原理 常见位运算解题方法&#xff1a; 1. 基础位运算&#xff1a; &&#xff1a;按位与&#xff0c;有0就是0 | &#xff1a;按位或&a…

C++分支语句

个人主页&#xff1a;PingdiGuo_guo 收录专栏&#xff1a;C干货专栏 大家新年快乐&#xff0c;今天&#xff0c;我们来了解一下分支语句。 文章目录 1.什么是分支语句 1.if语句 基本形式 用法说明 练习 2.if-else语句 基本形式 用法说明 练习 3.switch语句 基本形式…

LeetCode 热题 100 | 链表(中下)

目录 1 19. 删除链表的倒数第 N 个节点 2 24. 两两交换链表中的节点 3 25. K 个一组翻转链表 4 138. 随机链表的复制 菜鸟做题第三周&#xff0c;语言是 C 1 19. 删除链表的倒数第 N 个节点 到底是节点还是结点。。。 解题思路&#xff1a; 设置双指针 left 和 ri…

云安全的基本概念(基本目标与指导方针)

目录 一、云安全概念概述 1.1 概述 二、云安全的基本目标 2.1 安全策略开发模型 2.1.1 信息安全三元组 2.1.1.1 保密性(Confidentiality) 2.1.1.2 完整性(Integrity) 2.1.1.3 可用性(Availability) 2.1.2 信息安全三元组的局限性 2.2 其他信息安全属性 2.2.1 真实性 …

Java基础知识练习题

1.对Java源文件进行编译操作的命令是&#xff08;B&#xff09; A.Java B.javac C.where is java D.javaw 2.下列命令中&#xff0c;用来运行Java程序的是&#xff08;A&#xff09;A.java B. javadoc C. jar D. javac 分析&#xff1a; 对Java源程序进行编译的命令是J…

踩坑实录(Third Day)

临近年关&#xff0c;同事们该回家的也都回家了&#xff0c;所以我对工作的欲望不是很强烈&#xff0c;所以就主要是自己学习了一下&#xff0c;在 B 站看看视频&#xff0c;自己敲代码&#xff0c;所以今天没遇到什么坑&#xff0c;但是可以分享一下之前踩到的两个坑。 此为第…