我对ThreadLocal的理解

声明:小弟菜狗一个。对ThreadLocal的描写叙述和理解难免有所偏差

        近期由于须要深入的了解android的handler消息机制而去查看了Looper的源代码。众所周知在主线程中是不须要在程序猿在代码新建一个Looper对象的,由于在主线程创建时它就被创建出来了。所以就好奇它是怎么被创建出来的然后发现它跟ThreadLocal 有关于是便查看了该类的一些资料,但还是不太理解。于是便尝试自己去看一下源代码,然后就有了对ThreadLocal一个又一次的认识。先贴出Looper的代码:

        

   private Looper() {//MessageQueue对象会随Looper对象的创建而创建mQueue = new MessageQueue();mRun = true;mThread = Thread.currentThread();}


以下是Looper的代码,从代码中看出sThreadLocal是Looper的成员变量。它被new出来了。

当我第一次看到此代码的时候便产生了一个疑问,印象中不是说ThreadLocal对象都会绑定到一个线程中去的吗,若创建对象那么怎样确定它绑定到哪一个线程中呢(到后来我发现我的这样的想法是不正确的)。于是我便查看了ThreadLocal的代码。首先由于prepare调用到ThreadLocal的set方法。以下先查看下该方法的实现

public class Looper {private static final boolean DEBUG = false;private static final boolean localLOGV = DEBUG ? Config.LOGD : Config.LOGV;// sThreadLocal.get() will return null unless you've called prepare().private static final ThreadLocal sThreadLocal = new ThreadLocal();<span style="font-family: Arial, Helvetica, sans-serif;">   </span>
//该方法事实上就是将一个Looper对象设置进ThreadLocal的一个map中
public static final void prepare() {if (sThreadLocal.get() != null) {throw new RuntimeException("Only one Looper may be created per thread");}sThreadLocal.set(new Looper());}
public void set(T value) {
        Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);//ThreadLocalMap是ThreadLocal的内部类if (map != null)map.set(this, value);elsecreateMap(t, value);}

由ThreadLocal的方法不难看出set方法设置的值最后会与本ThreadLocal对象凑成一个键值对存放到它新建的ThreadLocalMap对象中的。

此时会注意到两个方法getMap(ThreadLocal tl,T t)和createMap(Thread t, T t)。

通过方法名就不难得出此双方法是跟ThreadLocalMap对象的获取和创建有关。

以下先观察ThreadLocal类中createMap方法的代码

 void createMap(Thread t, T firstValue) {t.threadLocals = new ThreadLocalMap(this, firstValue);}

通过代码能够知道此方法将一个新建的“键值对”为本ThreadLocal对象和要设置的value值的ThreadLocalMap对象赋值给当前线程的threadLocals变量。接下来查看Thread的代码。

  /* ThreadLocal values pertaining to this thread. This map is maintained* by the ThreadLocal class. */ThreadLocal.ThreadLocalMap threadLocals = null;
可见它是Thread的一个成员变量。至此当前线程的threadLocals就不为空并且是不会再被改变(由于从ThreadLocal的set方法中每一次在设置当前threadLocals的值之前都会先推断该对象是否为null)。


通过观察这一系列的代码能够了解到事实上在每个线程中都会有一个ThreadLocal.ThreadLocalMap变量,它与Map有点类似用于存放键值对,只是它的键是ThreadLocal对象,所以一个ThreadLocal对象仅仅能在它所在线程的ThreadLocal.ThreadLocalMap对象对象中存放有自己是key的一个值。事实上此时又会产生一个疑问这种以ThreadLocal

为key的键值对存放到Thread对象中的ThreadLocal.ThreadLocalMap中有什么意义呢?由于当我们失去了ThreadLocal对象之后就不能取出在线程中以该ThreadLocal的相应值。

事实上通过观察Looper的代码不难看出它的ThreadLocal sThreadLocal对象是一个静态变量。因此全部的Looper对象都在“共用”一个ThreadLocal 对象。因此确保了不同Looper的Looper.prepare方法在同一个线程的ThreadLocal.ThreadLocalMap中相应的值是一样的,这确保了一个线程中仅仅有一个Looper对象存放在当前线程的ThreadLocal.ThreadLocalMap中。


下面是Message、Message Queue、Handler、Looper类之间的大概的联系。

#### Handler消息机制

 

> #### Message 消息

Message msg = Message.obtain()Message msg = new Message()//获取Message类的两种方式

> #### Handler 

new Handler(){
handlerMessage(Message msg){
// 处理消息
}
}

> #### Handler的构造方法:

 

public Handler() {...
// 获取loopermLooper = Looper.myLooper();//事实上就是在本线程的ThreadLocal.Map中取looper对象if (mLooper == null) {throw new RuntimeException("Can't create handler inside thread that has not called Looper.prepare()");}mQueue = mLooper.mQueue;mCallback = null;}


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

> #### 主线程设置Looper。在ActivityThread类里面

public static final void main(String[] args) {....
// 1.主线程创建Looper Looper.prepareMainLooper();if (sMainThreadHandler == null) {sMainThreadHandler = new Handler();}ActivityThread thread = new ActivityThread();thread.attach(false);if (false) {Looper.myLooper().setMessageLogging(newLogPrinter(Log.DEBUG, "ActivityThread"));}Looper.loop();

> #### Looper

public static final void prepare() {//若在调此方法时本线程(非主线程)中不存在looper对象则会创建一个looper对象存放在线程的ThreadLocalMap对象中if (sThreadLocal.get() != null) {throw new RuntimeException("Only one Looper may be created per thread");}

// 3、在主线程中设置Looper, new Looper()里面创建了一个MessageQueue

        sThreadLocal.set(new Looper());public static final void prepareMainLooper() {// 2、调用prepareprepare();setMainLooper(myLooper());if (Process.supportsProcesses()) {myLooper().mQueue.mQuitAllowed = false;}}

> #### 主线程调用Looper.loop()方法,主线程就会堵塞,是一个死循环。使用管道(Pipe),是Linux中的一种进程间通信方式,使用了特殊的文件,有两个文件描写叙述符(一个是读取,一个是写入)

 

> #### 应用场景;主进程拿着读取描写叙述符等待读取,没有内容时就堵塞,还有一个进程拿写入描写叙述符去写内容,唤醒主进程,主进程拿着读取描写叙述符读取到内容。继续运行。

 

> #### Handler应用场景:Handler在主线程中创建,Looper会在死循环里等待取消息,1、没取到。就堵塞,2、一旦被子线程唤醒,取到消息。就把Message交给Handler处理。

子线程用Handler去发送消息。拿写入描写叙述符去写消息,唤醒主线程。

 public static final void loop() {...while (true) {
// 取消息。假设没有消息。就堵塞Message msg = queue.next(); // might block...msg.target.dispatchMessage(msg);...
}}


> #### Handler发送消息代码

public boolean sendMessageAtTime(Message msg, long uptimeMillis)
{....
// 把Message的target置为当前发送的Handler,以便Looper取到message后依据target把message分发给正确的Handler
msg.target = this;
// 往队列里面加入Messagesent = queue.enqueueMessage(msg, uptimeMillis);....}


> #### MessageQueue.enqueueMessage 代码

final boolean enqueueMessage(Message msg, long when) {...Message p = mMessages;if (p == null || when == 0 || when < p.when) {
// 当前发送的message须要立即被处理调。needWake唤醒状态置truemsg.next = p;mMessages = msg;needWake = mBlocked; // new head, might need to wake up} else {
// 当前发送的message被排队到其它message的后面。needWake唤醒状态置falseMessage prev = null;while (p != null && p.when <= when) {prev = p;p = p.next;}msg.next = prev.next;prev.next = msg;needWake = false; // still waiting on head, no need to wake up}}
// 是否唤醒主线程if (needWake) {nativeWake(mPtr);}return true;> #### Handler.dispatchMessage方法public void dispatchMessage(Message msg) {if (msg.callback != null) {handleCallback(msg);} else {if (mCallback != null) {if (mCallback.handleMessage(msg)) {return;}}
// 把Message交给Handler处理handleMessage(msg);}}




       

转载于:https://www.cnblogs.com/liguangsunls/p/6880337.html

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

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

相关文章

如何让开关打开_安卓手机如何打开USB调试模式

点击上方“手机互联” 关注我吧&#xff01;什么是USB调试模式&#xff1f;USB调试模式是 安卓手机提供的一个用于开发工作的功能。使用该功能可在设备和安卓手机之间复制数据、在移动设备上安装应用程序、读取日志数据等等。默认情况下&#xff0c;USB 调试模式是关闭的&#…

Redis持久化_Redis事务_Redis删除策略

Redis持久化 Redis包含3中持久化方案: RDB, AOF, RDB与AOF混合使用 RDB RDB: 将内存中数据生成快照形式, 将其保存到.rdb文件中, 关注点是数据 使用命令执行RDB过程 在保存.rdb文件之前还需要修改redis.conf配置文件, 修改项如下: dbfilename dump.rdb //配置.rdb文件名, …

第一次线下活动总结

公众号建立有了一段时间了&#xff0c;今天是我们筹划的第一次线下聚会活动&#xff0c;活动发起人是公众号的一个读者&#xff0c;是我们的前辈&#xff0c;也是这次活动的赞助商&#xff0c;非常感谢&#xff0c;支付了聚餐了费用&#xff0c;这次第一届活动&#xff0c;当然…

初学者选黑卡还是微单_零基础,一篇读懂单反和微单

许多小白在选购相机时&#xff0c;常常会纠结选微单还是选单反。那么这次就来一篇通俗讲解&#xff1a;单反和微单有什么区别&#xff1f;谁更好&#xff1f;应该怎么选择&#xff1f;一、单反和微单有什么区别&#xff1f;在了解单反和微单的区别之前&#xff0c;我们先要了解…

Redis核心配置_Redis高级数据类型使用

Redis核心配置 服务端配置 daemonize yes|no //服务器是否已守护进程方式运行 bind 127.0.0.1 //绑定主机 port 6379 //设置端口 databases 16 //设置数据库数量 loglevel debug|verbose|notice|warning //设置日志级别 logfile 端口号.log //设置日志文件名 maxclients 1 //…

xcode8注释快捷键失效问题

1. 首先按上图的指示&#xff0c;查看Add Documentation后面的快捷键是不是optioncommand/。 2. 如果发现不是默认的快捷键&#xff0c;可按快捷键command&#xff0c;打开Xcode偏好设置窗口&#xff0c;选中Key Bindings&#xff0c;搜索Add Documentation&#xff0c;便可修…

vant组件搜索并选择_Vant Weapp - 有赞出品的免费开源微信小程序组件库

轻量可靠的小程序UI组件库&#xff0c;主流移动组件库 Vant 的微信小程序版本。Vant Weapp 和 Vant 的区别之前推荐过的移动端web组件库 Vant 是 Vue.js 版本的&#xff0c;其对内承载了有赞所有核心业务&#xff0c;对外有十多万开发者在使用&#xff0c;一直是业界主流的移动…

走了,又回来了

今天换了个大的办公室&#xff0c;从100平增加到了300平&#xff0c;从宝安到南山&#xff0c;从旧环境到新环境&#xff0c;不是新的开始&#xff0c;是新的环境和心情。突然有点感慨&#xff0c;那时候从科技园出发&#xff0c;跟HP从深圳坐高铁去广州&#xff0c;在广州小蛮…

Flink-Java版单词计数(批处理流处理)

创建工程 pom.xml文件依赖如下: <dependencies><dependency><groupId>org.apache.flink</groupId><artifactId>flink-java</artifactId><version>1.10.1</version></dependency><!--依赖的一些组件需要 Scala 环境…

怎么做批注_BIM平台是什么?有何用?怎么用?

原标题&#xff1a;BIM平台是什么&#xff1f;有何用&#xff1f;怎么用&#xff1f;随着BIM技术的深入应用&#xff0c;我们也不再拘泥于单单BIM软件的使用&#xff0c;在BIM技术的广泛应用之下&#xff0c;BIM平台也逐渐成为了BIM技术的最佳体现&#xff0c;也成为了众多工程…

重入的问题

抛出个问题 有一个定时器,定时时间是1秒,然后里面有一个执行函数,里面的函数有时候执行500毫秒,有时候执行2秒,如果是这样的话,有没有问题,如果有问题,要怎么解决? 先说上面中断的问题 我之前写过一篇文章,专门说中断的,我又想起来之前说的那个重入和不重入的问题…

web前端性能优化

一、什么是前端性能优化从用户访问资源到资源完整的展现在用户面前的过程中&#xff0c;通过技术手段和优化策略&#xff0c;缩短每个步骤的处理时间从而提升整个资源的访问和呈现速度。二、为什么要做前端性能优提升网站性能&#xff0c;提升用户体验三、前端性能优化的原则1、…

Flink并行度优先级_集群操作常用指令_运行组件_任务提交流程_数据流图变化过程

Flink并行度优先级(从高到低) sum(1).setParallelism(1) env.setParallelism(1) ApacheFlinkDashboard任务添加并行度配置 flink-conf.yaml并行度配置 注: 处理输入输出时, 并行度默认为 1Flink集群常用指令 提交任务 run: 代表执行; c: 指定入口类; p: 并行度; host, post:…

招银网络笔试java_最新!!招银网络科技Java面经,整理附答案

作者&#xff1a;榨汁机2号 链接&#xff1a;https://www.nowcoder.com/discuss/1640193月12号现场面试的&#xff0c; 感觉好像所有人有3面的样子。到目前也没有消息&#xff0c;有消息的吱一声&#xff0c;让我早点死了这个心…..一面 1 Java的八大基本类型byte、short、int、…

CSS常用的元素居中方法

参考&#xff1a;CSS: 常用的元素居中方法 CSS居中布局总结 1.水平居中 &#xff08;1&#xff09;文本水平居中 text-align: center; &#xff08;2&#xff09;块级元素水平居中 ①设置margin margin: auto; ②display:inline-blocktext-align:center .parent {width: 400px;…

FlinkAPI_Environment_输入源_算子转化流程

Flink Environment getExecutionEnvironment() 根据当前平台, 获取对应的执行环境, 若未设置并行度, 使用 flink-conf.yaml 中的并行度配置, 默认 1. StreamExecutionEnvironment env StreamExecutionEnvironment.getExecutionEnvironment();createLocalEnviroment() 创建本地…

第2章 Linux内核模块

宏内核和微内核继续前面第一章的知识&#xff0c;虽然有点啰嗦&#xff0c;既然啰嗦了就继续啰嗦下去吧&#xff0c;也是给第一章的内容增加解释。我们知道内核如果按种类来划分的话&#xff0c;可以分为宏内核和微内核&#xff0c;微内核是一个比较先进的内核&#xff0c;我不…

BZOJ 1137 半平面交

半平面交的板子 //By SiriusRen #include <bits/stdc.h> #define double long double using namespace std; const int N100050; const double eps1e-10; int n,m,xx,yy,tot; double Ans; vector<int>vec[N]; struct Point{double x,y;}point[N]; struct Line{Poin…

的注册表怎么才能删干净_油烟净化器怎么清洗才能清理干净呢?

油烟机的净化器的主要功能是过滤厨房里的油烟&#xff0c;因为它总是处理油烟&#xff0c;所以清洗净化器很麻烦&#xff0c;那么如何清洗呢&#xff1f;经常清洗油烟净化器是很有必要的&#xff0c;但清洗起来很麻烦&#xff0c;清洗起来也不容易。今天&#xff0c;我想告诉大…

Flink-Sink_将结果输出到Kafka_Redis_ES_Mysql中

Sink 将计算好结果输出到外部系统, 调用 addSink()传入指定的SinkFunction() 将结果输出到 Kafka 中将结果输出到 Redis 中将结果输出到 ES 中将结果输出到 Mysql 中: 事先创建好表结构 pom.xml 事先导入对应的 connector: <dependencies><dependency><group…