线程同步和线程异步的区别

线程同步和线程异步的区别19、Handler.Callback.handleMessage 和 Handler.handleMessage 有什么不一样?为什么这么设计?前言对于handler,你会想到什么呢?面试必问?项目常用?体系庞大?既然它如此重要,不知对面的你了解它多深

19、Handler.Callback.handleMessage 和 Handler.handleMessage 有什么不一样?为什么这么设计?   前言   对于handler,你会想到什么呢?   面试必问?项目常用?体系庞大?   既然它如此重要,不知对面的你了解它多深呢?今天就和大家一起打破砂锅问到底,看看这口砂锅的底到底在哪里。   二十七问,从问题的角度再读Handler。   知识梳理   
线程同步和线程异步的区别   1、Handler被设计出来的原因?有什么用?   一种东西被设计出来肯定就有它存在的意义,而的意义就是切换线程。   作为消息机制的主要成员,它管理着所有与界面有关的消息事件,常见的使用场景有:跨进程之后的界面消息处理。   比如Activity的启动,就是AMS在进行进程间通信的时候,通过Binder线程 将消息发送给的消息处理者,然后再将消息分发给主线程中去执行。网络交互后切换到主线程进行UI更新   当子线程网络操作之后,需要切换到主线程进行UI更新。   总之一句话,的存在就是为了解决在子线程中无法访问UI的问题。   2、为什么建议子线程不访问(更新)UI?   因为中的UI控件不是线程安全的,如果多线程访问UI控件那还不乱套了。   那为什么不加锁呢?。本身UI控件就是离用户比较近的一个组件,加锁之后自然会发生阻塞,那么UI访问的效率会降低,最终反应到用户端就是这个手机有点卡。。本身UI访问时一个比较简单的操作逻辑,直接创建UI,修改UI即可。如果加锁之后就让这个UI访问的逻辑变得很复杂,没必要。   所以,Android设计出了 来处理UI操作,再搭配上Handler,是一个比较合适的解决方案。   3、子线程访问UI的 崩溃原因 和 解决办法?   崩溃发生在ViewRootImpl类的方法中:   其实就是判断了当前线程 是否是 创建时候的线程,如果不是,就会崩溃。   而ViewRootImpl创建的时机就是界面被绘制的时候,也就是onResume之后,所以如果在子线程进行UI更新,就会发现当前线程(子线程)和View创建的线程(主线程)不是同一个线程,发生崩溃。   解决办法有三种:在新建视图的线程进行这个视图的UI更新,主线程创建View,主线程更新View。在创建之前进行子线程的UI更新,比如onCreate方法中进行子线程更新UI。子线程切换到主线程进行UI更新,比如方法。   4、MessageQueue是干嘛呢?用的什么数据结构来存储数据?   看名字应该是个队列结构,队列的特点是什么?,一般在队尾增加数据,在队首进行取数据或者删除数据。   那中的消息似乎也满足这样的特点,先发的消息肯定就会先被处理。但是,中还有比较特殊的情况,比如延时消息。   延时消息的存在就让这个队列有些特殊性了,并不能完全保证先进先出,而是需要根据时间来判断,所以中采用了链表的形式来实现这个队列,也方便了数据的插入。   来一起看看消息的发送过程,无论是哪种方法发送消息,都会走到方法   方法主要计算了消息需要被处理的时间,如果为0,那么消息的处理时间就是当前时间。   然后就是关键方法。   不懂得地方先不看,只看我们想看的:首先设置了的when字段,也就是代表了这个消息的处理时间然后判断当前队列是不是为空,是不是即时消息,是不是执行时间when大于表头的消息时间,满足任意一个,就把当前消息msg插入到表头。否则,就需要遍历这个队列,也就是,找出when小于某个节点的when,找到后插入。   好了,其他内容暂且不看,总之,插入消息就是通过消息的执行时间,也就是字段,来找到合适的位置插入链表。   具体方法就是通过死循环,使用快慢指针p和prev,每次向后移动一格,直到找到某个节点p的when大于我们要插入消息的when字段,则插入到p和prev之间。 或者遍历到链表结束,插入到链表结尾。   所以,就是一个用于存储消息、用链表实现的特殊队列结构。   5、延迟消息是怎么实现的?   总结上述内容,延迟消息的实现主要跟消息的统一存储方法有关,也就是上文说过的方法。   无论是即时消息还是延迟消息,都是计算出具体的时间,然后作为消息的when字段进程赋值。   然后在MessageQueue中找到合适的位置(安排when小到大排列),并将消息插入到中。   这样,就是一个按照消息时间排列的一个链表结构。   6、MessageQueue的消息怎么被取出来的?   刚才说过了消息的存储,接下来看看消息的取出,也就是方法。   奇怪,为什么取消息也是用的死循环呢?   其实死循环就是为了保证一定要返回一条消息,如果没有可用消息,那么就阻塞在这里,一直到有新消息的到来。   其中,方法就是阻塞方法,参数就是阻塞的时间。   那什么时候会阻塞呢?两种情况:1、有消息,但是当前时间小于消息执行时间,也就是代码中的这一句:   这时候阻塞时间就是消息时间减去当前时间,然后进入下一次循环,阻塞。2、没有消息的时候,也就是上述代码的最后一句:   就代表一直阻塞。   7、MessageQueue没有消息时候会怎样?阻塞之后怎么唤醒呢?说说pipe/epoll机制?   接着上文的逻辑,当消息不可用或者没有消息的时候就会阻塞在next方法,而阻塞的办法是通过pipe/epoll机制   是一种IO多路复用的机制,具体逻辑就是一个进程可以监视多个描述符,当某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作,这个读写操作是阻塞的。在Android中,会创建一个来处理阻塞和唤醒。当消息队列为空,管道的读端等待管道中有新内容可读,就会通过机制进入阻塞状态。当有消息要处理,就会通过管道的写端写入内容,唤醒主线程。   那什么时候会怎么唤醒消息队列线程呢?   还记得刚才插入消息的方法中有个字段吗,很明显,这个就是表示是否唤醒的字段。   其中还有个字段是,看字面意思是阻塞的意思,去代码里面找找:   在消息的方法中,有两个地方对赋值:当到消息的时候,赋值为,表示不阻塞。当没有消息要处理,也没有要处理的时候,赋值为,表示阻塞。   好了,确实这个字段就表示是否阻塞的意思,再去看看方法中,唤醒机制:   当链表为空或者时间小于表头消息时间,那么就插入表头,并且设置是否唤醒为。   再结合上述的例子,也就是当有新消息要插入表头了,这时候如果之前是阻塞状态(mBlocked=true),那么就要唤醒线程了。否则,就需要取链表中找到某个节点并插入消息,在这之前需要赋值   也就是在插入消息之前,需要判断是否阻塞,并且表头是不是屏障消息,并且当前消息是不是异步消息。 也就是如果现在是同步屏障模式下,那么要插入的消息又刚好是异步消息,那就不用管插入消息问题了,直接唤醒线程,因为异步消息需要先执行。最后一点,是在循环里,如果发现之前就存在异步消息,那就还是设置是否唤醒为。   意思就是,如果之前有异步消息了,那肯定之前就唤醒过了,这时候就不需要再次唤醒了。   最后根据的值,决定是否调用方法唤醒方法。   8、同步屏障和异步消息是怎么实现的?   其实在机制中,有三种消息类型:。也就是普通的消息。。通过setAsynchronous(true)设置的消息。。通过postSyncBarrier方法添加的消息,特点是target为空,也就是没有对应的handler。   这三者之间的关系如何呢?正常情况下,同步消息和异步消息都是正常被处理,也就是根据时间when来取消息,处理消息。当遇到同步屏障消息的时候,就开始从消息队列里面去找异步消息,找到了再根据时间决定阻塞还是返回消息。   也就是说同步屏障消息不会被返回,他只是一个标志,一个工具,遇到它就代表要去先行处理异步消息了。   所以同步屏障和异步消息的存在的意义就在于有些消息需要。   9、同步屏障和异步消息有具体的使用场景吗?   使用场景就很多了,比如绘制方法。   在该方法中加入了同步屏障,后续加入一个异步消息,最后会执行到,用于申请VSYNC信号。   10、Message消息被分发之后会怎么处理?消息怎么复用的?   再看看loop方法,在消息被分发之后,也就是执行了方法之后,还偷偷做了一个操作——。   在方法中,释放了所有资源,然后将当前的空消息插入到sPool表头。   这里的就是一个消息对象池,它也是一个链表结构的消息,最大长度为50。   那么Message又是怎么复用的呢?在Message的实例化方法中:   直接复用消息池中的第一条消息,然后sPool指向下一个节点,消息池数量减一。   11、Looper是干嘛呢?怎么当前线程的Looper?为什么不直接用Map存储线程和对象呢?   在Handler发送消息之后,消息就被存储到中,而就是一个管理消息队列的角色。 Looper会从中不断的查找消息,也就是loop方法,并将消息交回给Handler进行处理。   而Looper的就是通过机制:   通过方法创建Looper并且加入到sThreadLocal中,通过方法从sThreadLocal中Looper。   12、ThreadLocal运行机制?这种机制设计的好处?   下面就具体说说运行机制。   从类中的get和set方法可以大致看出来,有一个变量,这个变量存储着键值对形式的数据。为this,也就是当前ThreadLocal变量。为T,也就是要存储的值。   然后继续看看哪来的,也就是getMap方法:   原来这个变量是存储在线程类Thread中的。   所以的基本机制就搞清楚了:   在每个线程中都有一个threadLocals变量,这个变量存储着ThreadLocal和对应的需要保存的对象。   这样带来的好处就是,在不同的线程,访问同一个ThreadLocal对象,但是能到的值却不一样。   挺神奇的是不是,其实就是其内部到的Map不同,Map和Thread绑定,所以虽然访问的是同一个对象,但是访问的Map却不是同一个,所以取得值也不一样。   这样做有什么好处呢?为什么不直接用Map存储线程和对象呢?   打个比方:   
线程同步和线程异步的区别   就是老师。就是同学。(需要的值)就是铅笔。   现在老师买了一批铅笔,然后想把这些铅笔发给同学们,怎么发呢?两种办法:1、老师把每个铅笔上写好每个同学的名字,放到一个大盒子里面去(map),用的时候就让同学们自己来找。   这种做法就是Map里面存储的是,然后用的时候通过同学来从这个Map里找铅笔。   这种做法就有点像使用一个Map,存储所有的线程和对象,不好的地方就在于会很混乱,每个线程之间有了联系,也容易造成内存泄漏。2、老师把每个铅笔直接发给每个同学,放到同学的口袋里(map),用的时候每个同学从口袋里面拿出铅笔就可以了。   这种做法就是Map里面存储的是,然后用的时候老师说一声,同学只需要从口袋里拿出来就行了。   很明显这种做法更科学,这也就是的做法,因为铅笔本身就是同学自己在用,所以一开始就把铅笔交给同学自己保管是最好的,每个同学之间进行隔离。   13、还有哪些地方运用到了ThreadLocal机制?   比如:Choreographer。   主要是主线程用的,用于配合 中断信号。   所以这里使用更多的意义在于完成线程单例的功能。   14、可以多次创建Looper吗?   Looper的创建是通过方法实现的,而在prepare方法中就判断了,当前线程是否存在Looper对象,如果有,就会直接抛出异常:   所以同一个线程,只能创建一个,多次创建会报错。   15、Looper中的quitAllowed字段是啥?有什么用?   按照字面意思就是是否允许退出,我们看看他都在哪些地方用到了:   哦,就是这个方法用到了,如果这个字段为,代表不允许退出,就会报错。   但是这个方法又是干嘛的呢?从来没用过呢。 还有这个又是啥呢?   其实看名字就差不多能了解了,quit方法就是退出消息队列,终止消息循环。首先设置了字段为true。然后判断是否安全退出,如果安全退出,就执行方法,它内部的逻辑是清空所有的延迟消息,之前没处理的非延迟消息还是需要取处理,然后设置非延迟消息的下一个节点为空(p.next=null)。如果不是安全退出,就执行方法,直接清空所有的消息,然后设置消息队列指向空(mMessages = null)   然后看看当调用quit方法之后,消息的发送和处理:   当调用了quit方法之后,为true,消息就发不出去了,会报错。   再看看消息的处理,loop和next方法:   很明显,当为true的时候,next方法返回null,那么loop方法中就会退出死循环。   那么这个方法一般是什么时候使用呢?主线程中,一般情况下肯定不能退出,因为退出后主线程就停止了。所以是当APP需要退出的时候,就会调用quit方法,涉及到的消息是EXIT_APPLICATION,大家可以搜索下。子线程中,如果消息都处理完了,就需要调用quit方法停止消息循环。   16、Looper.loop方法是死循环,为什么不会卡死(ANR)?   我大致总结下:1、主线程本身就是需要一只运行的,因为要处理各个View,界面变化。所以需要这个死循环来保证主线程一直执行下去,不会被退出。2、真正会卡死的操作是在某个消息处理的时候操作时间过长,导致掉帧、ANR,而不是loop方法本身。3、在主线程以外,会有其他的线程来处理接受其他进程的事件,比如,会接受AMS发送来的事件4、在收到跨进程消息后,会交给主线程的再进行消息分发。所以Activity的生命周期都是依靠主线程的,当收到不同Message时则采用相应措施,比如收到,则调用方法,最终执行到onCreate方法。5、当没有消息的时候,会阻塞在loop的中的方法里,此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生。所以死循环也不会特别消耗CPU资源。   17、Message是怎么找到它所属的Handler然后进行分发的?   在loop方法中,找到要处理的,然后调用了这么一句代码处理消息:   所以是将消息交给了来处理,那么这个target是啥呢?   找找它的来头:   在使用Hanlder发送消息的时候,会设置,所以target就是当初把消息加到消息队列的那个Handler。   18、Handler 的 post(Runnable) 与 sendMessage 有什么区别   Hanlder中主要的发送消息可以分为两种:post(Runnable)sendMessage   通过post的源码可知,其实的区别就在于:   post方法给Message设置了一个。   那么这个callback有什么用呢?我们再转到消息处理的方法中看看:   这段代码可以分为三部分看:1、如果不为空,也就是通过post方法发送消息的时候,会把消息交给这个msg.callback进行处理,然后就没有后续了。2、如果为空,也就是通过sendMessage发送消息的时候,会判断Handler当前的mCallback是否为空,如果不为空就交给Handler.Callback.handleMessage处理。3、如果返回true,则无后续了。4、如果返回false,则调用handler类重写的handleMessage方法。   所以post(Runnable) 与 sendMessage的区别就在于后续消息的处理方式,是交给还是 或者。   19、Handler.Callback.handleMessage 和 Handler.handleMessage 有什么不一样?为什么这么设计?   接着上面的代码说,这两个处理方法的区别在于方法是否返回true:如果为,则不再执行Handler.handleMessage如果为,则两个方法都要执行。   那么什么时候有,什么时候没有呢?这涉及到两种Hanlder的 创建方式:   常用的方法就是第1种,派生一个Handler的子类并重写handleMessage方法。 而第2种就是系统给我们提供了一种不需要派生子类的使用方法,只需要传入一个Callback即可。   20、Handler、Looper、MessageQueue、线程是一一对应关系吗?   一个线程只会有一个对象,所以线程和Looper是一一对应的。对象是在new Looper的时候创建的,所以Looper和MessageQueue是一一对应的。的作用只是将消息加到MessageQueue中,并后续取出消息后,根据消息的target字段分发给当初的那个handler,所以Handler对于Looper是可以多对一的,也就是多个Hanlder对象都可以用同一个线程、同一个Looper、同一个MessageQueue。   总结:Looper、MessageQueue、线程是一一对应关系,而他们与Handler是可以一对多的。   21、ActivityThread中做了哪些关于Handler的工作?(为什么主线程不需要单独创建Looper)   主要做了两件事:1、在main方法中,创建了主线程的和,并且调用loop方法开启了主线程的消息循环。   2、创建了一个Handler来进行四大组件的启动停止等事件处理   22、IdleHandler是啥?有什么使用场景?   之前说过,当没有消息的时候,就会阻塞在next方法中,其实在阻塞之前,还会做一件事,就是检查是否存在,如果有,就会去执行它的方法。   当没有消息处理的时候,就会去处理这个集合里面的每个对象,并调用其方法。 最后根据返回值判断是否用完删除当前的。   然后看看是怎么加进去的:   ok,综上所述,就是当消息队列里面没有当前要处理的消息了,需要堵塞之前,可以做一些空闲任务的处理。   常见的使用场景有:。   我们一般会把一些事件(比如界面view的绘制、赋值)放到方法或者方法中。 但是这两个方法其实都是在界面绘制之前调用的,也就是说一定程度上这两个方法的耗时会影响到启动时间。   所以我们可以把一些操作放到中,也就是界面绘制完成之后才去调用,这样就能减少启动时间了。   但是,这里需要注意下可能会有坑。   如果使用不当,会一直不执行,比如在里面无限制的直接或者间接调用。   其原因就在于onDraw方法中执行,会添加一个同步屏障消息,在等到异步消息之前,会阻塞在next方法,而等到异步任务之后又会执行onDraw方法,从而无限循环。   23、HandlerThread是啥?有什么使用场景?   直接看源码:   哦,原来如此。就是一个封装了Looper的Thread类。   就是为了让我们在子线程里面更方便的使用Handler。   这里的加锁就是为了保证线程安全,当前线程的Looper对象,成功之后再通过方法唤醒其他线程,那哪里调用了方法呢?   就是方法,所以wait的意思就是等待Looper创建好,那边创建好之后再通知这边正确返回Looper。   24、IntentService是啥?有什么使用场景?   老规矩,直接看源码:   理一下这个源码:首先,这是一个并且内部维护了一个,也就是有完整的Looper在运行。还维护了一个子线程的。启动Service后,会通过Handler执行方法。完成任务后,会自动执行停止当前Service。   所以,这就是一个可以在子线程进行耗时任务,并且在任务执行后自动停止的Service。   25、BlockCanary使用过吗?说说原理   是一个用来检测应用卡顿耗时的三方库。   上文说过,View的绘制也是通过Handler来执行的,所以如果能知道每次Handler处理消息的时间,就能知道每次绘制的耗时了? 那Handler消息的处理时间怎么呢?   再去loop方法中找找细节:   可以发现,loop方法内有一个类,在处理消息的前后分别打印了两次日志。   那我们把这个日志类替换成我们自己的,然后统计两次打印日志的时间不就相当于处理消息的时间了?   这就是BlockCanary的原理。   26、说说Hanlder内存泄露问题。   这也是常常被问的一个问题,内存泄露的原因是什么?   ”内部类持有了外部类的引用,也就是Hanlder持有了Activity的引用,从而导致无法被回收呗。”   其实这样回答是错误的,或者说没回答到点子上。   我们必须找到那个最终的引用者,不会被回收的引用者,其实就是主线程,这条完整引用链应该是这样:   主线程 —> threadlocal —> Looper —> MessageQueue —> Message —> Handler —> Activity   27、利用Handler机制设计一个不崩溃的App?   主线程崩溃,其实都是发生在消息的处理内,包括生命周期、界面绘制。   所以如果我们能控制这个过程,并且在发生崩溃后重新开启消息循环,那么主线程就能继续运行。   总结   大家应该可以发现,有一个问题常被问,但是全篇都没有提,那就是:   Hanlder机制的运行原理。   之所以不提这个问题,是因为要回答好这个问题需要大量知识储备,希望屏幕前的你在读完这篇之后,再结合自己的知识库,形成自己的。   最后   小编在网上收集了一些 Android 开发相关的学习文档、面试题、Android 核心笔记等等学习文档,希望能帮助到大家学习提升,如有需要学习参考的可以直接去访问我 GitHub 或直接下方小卡片进行 查阅。   
线程同步和线程异步的区别
线程同步和线程异步的区别

2024最新激活全家桶教程,稳定运行到2099年,请移步至置顶文章:https://sigusoft.com/99576.html

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。 文章由激活谷谷主-小谷整理,转载请注明出处:https://sigusoft.com/82865.html

(0)
上一篇 2024年 7月 28日 下午4:21
下一篇 2024年 7月 28日 下午4:24

相关推荐

关注微信