你真的了解Handler吗?

Handler,一个面试中常问的高频词汇。

咱们想想这个知识点一般是怎样调查的?请阐明一下Handler的原理?

不不不,这个问题现已烂大街了,我要是面试官,我会这么问线程池原理

咱们知道在Handler中,存在一个办法叫 sendMessageDelay , 效果是延时发送音讯,请阐明一下Handler是怎样结束延时发送音讯的?
Looper.loop是一源码之家个死循环,拿不到需求处理的Message就会堵塞,那在UI线程中为什么不会导致ANR?

也请各位线程和进程的差异是什么读者先自己考线程堵塞的处理办法虑一下这两个问题linux重启指令,换做是你该怎线程数越多越好吗样答复。

Ha线程是什么意思ndler

咱们先从Handler的界说来知道它,先上谷歌原线程堵塞文:

/**
* A Handler allows you to send and process {@linux必学的60个指令link Message} and Runnable
* objects源码精灵 associated with a thread's {@link MessageQueue}.  Each源码之家 Handler
* instance is associated with a single thread and that thread's message
* queue.  When you crelinux重启指令ate a new Handler, it is bound to the thread /
* message q线程堵塞会占用cpu吗ueue of the thread源码共享网 that is creating it -- from that point on线程池回绝战略,
* it will源码 deliver messagLinuxes and runnlinux指令ables to that message queue and execute
* them as they come out of the message queue.
*
* <p>Thlinux检查进程指令ere are twlinux常用指令o main uses for a Handler: (1) to schedule message线程池面试题s and
* runnables to be exec线程堵塞和等候uted at some point in the future; and (2源码之家) to enqueue
* an action to be performed on a different t线程堵塞和非堵塞hread t线程堵塞的处理办法han yo源码资源站ur own.
*
* <p>Scheduling messages i线程池参数详解s a线程ccomplished with the
* {@link #post}, {@link #postAtTime(Runnable, long)linux检查进程指令},
* {@link #postDelayed}, {@li线程池怎样确保线程履行次序nk #sendEmptyMessage},
* {@link #sen线程池创立的四种dMessage}, {@link #sendMessageAtTime}, and
* {@link #sendMessageDelayed} methods.  The <e线程池的七个参数m>post</em> versions allow
* y线程数越多越好吗ou to enqlinux必学的60个指令ueue Runnable objects to be called b线程堵塞怎样处理y the message queue when
* the线程撕裂者y are received; the <em>sendMessage</em> versionlinux指令s allow you to enqueue
* a {@link Message} object containing a bundle of data that will be
* processed by源码是什么意思 the Hand线程堵塞原因ler's {@llinux是什么操作体系ink #handleMessa线程池的创立办法有几种ge} method (requiring that
* youlinux必学的60个指令 implement a subclass of Handler).
*
* &llinux体系t;p线程是什么意思>When posting or sending to a Handler, you can either
* allowlinux体系 the item to be processed as soon as the message queue is ready
* to do so, or specify a delay before it gets p线程池原理rocessed or absolute time for
* it to be proce线程撕裂者ssed.  The latter two allow you to implement timeouts,
* ticks, and other timing-based behavior.
*
* <p>When a
* process is created for your application, its main thread is dedicated to
* running a message queue t线程池创立的四种hat takes care of managing the top-线程池有几种level
* appllinux检查进程指令ication objects (activities, broadcast receivers, et源码资源站c) and any windows
* they create.  You can create your own threads, and communlinux指令icate back with
* the main application thread through a Handler.  This is done by calling
* the same <em>post</em> or <em>sendMessage</em> methods as befo线程数越多越好吗re, but frlinux必学的60个指令om
* your new thread.  The given Runnable or Message will then be scheduled
* in the H线程池原理andl源码精灵er's message queue and processed when appropriate.linux必学的60个指令
*/

下面由我这枚英语渣上线,强行翻译一波。

  1. Handler是用来结合线程的音讯部队来发送、线程堵塞会占用cpu吗处理Message政策Runnable政策的东西。每一个Handler实例化之后会相关一个线程和该线程的音讯部队。当你创立一个Handle源码年代训练怎样样r的时分,它就会自动绑定到到地点的线程或线程的音讯部队,并陆续把Message/Runnable分发到音讯部队,然后在它源码本钱们出队的时分去实施。

  2. Handler首要有两个用处: (1) 调度在将来某个时分实施的MessageRunnable。(源码交易平台2)把需求在另一个线程实施的操作参加到音讯部队中去。

  3. post runnablesend messagehandler时,您可以在音讯部队准备就绪后当即处理该事务。也可以推迟一段时刻实施,或许指定某个特定时刻去实施。


咱们先从Handler的结构办法来知道一下它:

public Handler(@NonNull Looper looper, @Nullable Callback callbaclinux是什么操作体系k, boolean async)

Hanlinux重启指令dler的结构办法有很多个,但终究调用的便是上述结构办法。

老规矩,先上官方阐明,再上linux体系装置学渣翻译。

* Use the provided {@link Looper} instead of the线程堵塞和等候 defaul源码年代训练怎样样t one and take a calinuxllback
* interface in which to handle mesLinuxsages.  Also set whether the handler
* should be asynchronous.
*
* Handlers are synchronous by default unless this c线程池的创立办法有几种onstructor is used to make
* one that is strictly asynchronous.
*
* Asynchronous messages represent interrupts or events that do not require global ordering
* with resp线程是什么意思ect to synchronous messages.  Async线程是什么意思hron线程池ous messages are not subject tolinux体系
* the synchronizatilinux体系装置on barriers introduced by conditions such as display vsync.
  1. 运用供线程池参数详解应的L线程堵塞和等候ooper而不是默许的Looper,并运用回调接口来处理音讯。还设置处理程序是否应该是异步的。

  2. 默许情况源码下,Handler是同步的源码是什么意思,除非此结构函数用于生成严厉异步的Handler

  3. 异步音讯指的是不需求进行全局排序的连续或作业。异步音讯不受同步阻碍(比方display vsync)的影响。


Handler中的办法首要分为以下两类:

  1. 获取及查线程池面试题询音讯,线程安全比方 o线程堵塞和非堵塞btainMessage(int what),hasMessages(int what)

  2. 将mes线程池有几种sage或runnable增加/移出音讯部队,比方 postAtTime(@NonNull Runnable r, long uptimeMillis),sendEmptyMessageDelayed(int what, long d线程池创立的四种elayMillis)

在这些办法中,咱们要害需求注重一下enqueueMessage这个办法。

为什么呢?

无论是 postAtTimesendMessageDelayed仍是其他的post、send办法,它们终究都会调到enqueueMessage这个办法线程安全里去。线程池面试题

比方:

public final boolean sendMessageDelayed(@NonNul源码精灵l Message msg, long delayMillis) {
if (delayMillis < 0)线程池 {
delayMi线程堵塞的处理办法llis =linux常用指令 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMi源码llis);
}

可以看到,selinux必学的60个指令ndMessageDelayed办法里将推迟时刻转换为音讯触发的必定时刻,线程池作业原理终究调用的是sendMessageAtTime办法。

pub线程池作业原理lic boolean sendMessageAtTime(@NonNull Message msg, long upt线程池的七个参数imeMillis) {
MessageQueue queue = mQuelinux重启指令ue;
if (queue == nu线程池回绝战略ll) {
RuntimeException线程池怎样确保线程履行次序 e = new RuntimeElinux指令xception(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}

而sLinuxendMessageAtTime办法调用了enqueueMessage办法。

private boolean enqu线程池作业原理eueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
msg.target = this;
msg.w线程堵塞和死锁orkSour线程堵塞是什么意思ceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}

elinux重启指令nqueueMessage办法直接将me线程堵塞会占用cpu吗ssage交给了MessageQueue去实施。

Message

在剖析MessageQueue之前,咱们应该先来知linux操作体系基础知识道一下Message这个音讯载体类。

老规矩,先线程是什么意思从界说看起:

* Defines a messalinux必学的60个指令ge containing a description an线程堵塞原因d arbitrary data object that can be
* sent to a {@link Handler}.  This object contains two extra int fields an源码之家d a线程池怎样确保线程履行次序n
* extra objec线程和进程的差异是什么t fiel线程池d that allow you t线程堵塞和非堵塞o not do allocations in many cases.
*
* <p class="note">While the constructor of Message is public, th源码交易平台e best way to get
* one of these is to call {@link #obtain Message.obtain()} or one of the
*线程池 {@link Handler#obtainMessage Handler.obtai源码本钱nMessage()} methods, whic线程池面试题h will pull
* them from a pool of recycled objects.</p>

下面是渣翻译:

  1. 界说一条包括线程池的创立办法有几种描绘和任意数据政策的音讯,该政策可以发送到Handler。此政策linux指令包括两个额外的in线程池的创立办法有几种t字段和一个额外的线程堵塞object字段。

  2. 虽然Message的结构办法是public,但获取一个Message的最好的办法是调用Message.obtain或许Handler.obtainMessage办法,这些办法会从可收回的线程池中获取Message政策。


咱们来知道一下Message里的字段:

public f源码本钱inal class Messa线程撕裂者ge implements Parcela线程池创立的四种ble {
//用户界说的标识码,以便接纳者可以辨认这条音讯是关于什么的。
//每个Handler都有自己源码年代的命名空间,因而不需求忧linux体系装置虑标linux是什么操作体系识码与其他Handler的抵触。
public in线程池面试题t what源码共享网;
//假定只需求存储几个整数值,则arg1和arg2是运用setData(Bundle) setData()的低成本替代方案。
public int arg1线程堵塞的办法有哪些;
public int arg2;
//要发送给接纳者的任linux意政策
public Object obj;
//通常在跨进程通讯中运用,让服务端可以得到客户端的信使政策,linux重启指令给客户端发音讯
public Messenger源码年代 replyTo;
//可选字段,指示发送音讯的uid,仅对Messen线程池参数详解ger发布的音讯有用,否则默许为-1
publi线程池面试题c int sendingUid = UID_NONE;
//可选字段,指示导致此音讯排队的uid。
public int workSourceUid = UID_NONE;
//此标识在音讯入队时设置,在创立或许获取新音讯线程池参数详解时根除
//查验入队或收回已在运用的音讯会发送差错
/*packag线程池原理e*/ static final int FLAG_IN_USE = 1 << 0;
//设置是否是异步音讯
/*package*/线程池原理 static final int FLAG_ASYNCHRONOUS = 1 << 1;
//copyFrom办法中要根除的标志
/*package*/ static final int FLAGS_TO_CLEAR_ON_COPY_FROM = FLAG_IN_USE;
@UnsupportedAppUsage
/*package源码*/ int flags;
//Mess线程和进程的差异是什么age发送的时刻,基于SystemClock#uptimeMillis
@UnsupportedAppUsage
@VisibleForTesting(visibility =线程池作业原理 VisibleForTesting.Visibility.PACKlinux操作体系基础知识AGE)
public long when;
/*package*/ Bundle data源码共享网;
//政策handler
@UnsupportedAppUsage
/*package*/ Handl源码编辑器er target;
@UnsupportedAppUsage
/*package线程数越多越好吗*/ Runnable callback;
//运用单向链表储存下一个源码音讯
@UnsupportedAppUsage
/*package*/ Message next;
}

在Message中,咱们需求注重一下Message的收回机制。

先来看下r线程ecyclerUnchecked办法:

void recycleUnchecked线程是什么意思() {
// Mark the message as in use while it remains in the recycled o线程堵塞和非堵塞bject po线程池回绝战略ol.
// Clear out all other details.
flags = FLAG_IN_USE;
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
sendingUid = UID_NONE线程堵塞情况;
workSourceUid = UID_NONE;
when = 0;
target = null;
callback = null;
data = null;
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}

在这个办法中,有三个要害变量。

  1. sPoolSync :首要是给Message加一个政策锁,不容许多个线程一起访线程的几种情况问Message类和recycleUnchecked办法。
  2. sPool:存储咱们线程池作业原理循环运用Message的单链表。这儿sPool只是链表的头节点。
  3. sPoolSize:单链表的链表的长度,即存储的Message政策的个线程堵塞和非堵塞数。

当咱们调用recycleUnchecked办法时,线程堵塞是什么意思首要会将当时Message方源码针的特征清空。然后判别Message是否已抵达缓存的上限(50个),假定没有,将当时的Message政策置于链表的头部。

那么取缓存的操作呢?

咱们线程堵塞和非堵塞来看下obtain办法:

public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.ne源码xt;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}

可以看出,Message会查验取出sPool链表的榜首个元素,并将sPool的头元素往后移动一位。假定线程堵塞原因sPool链表为空,将会回来一个新的Message政策。

Message里供给obtain办法获取Message方源码共享网针,使得Message到了重复的运用,减少了每次获取Message时去申请空间的时刻。一线程池面试题同,这样也不会永无止境的去创立新政策,减小了Jvm废物收回的压力,提高了功率。

MessageQueue

MessageQueue用于保存由Looper发送的音讯的列表。音讯不会直接增加到音讯部队,而是经过Handler政策中相关的Lo源码精灵oper里的MessageQueue结束增加的动作。

您可以运用Lo线程池有几种ope线程堵塞原因r.myQueue()检索当时线程的MessageQueue。

咱们先来看看Messaglinux重启指令eQueue怎样结束源码年代增加一个Me线程堵塞怎样处理ssage的操作。

boolean enqueueMessage(Message m源码本钱sg, long when) {
//判别msg是否有t源码arget特征以源码年代训练怎样样及是否正在运用中
if (msg.target == null) {
throw new IllegalArgumentExce线程数越多越好吗ption("Message must have a target.");
}
if (msg.isInUse()) {
throw n线程池参数详解ew IllegalStateException(msg线程 + " This message is already线程和进程的差异是什么 in use.");
}
syn源码chronized (this) {
if (mlinux操作体系基础知识Q线程堵塞uitting) {
IllegalStateExcepti线程池的七个参数on elinux体系 = new IllegalStateE线程堵塞xc线程池eption(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(),线程堵塞怎样处理 e);
msg.recycle();
return false;
}
/linux是什么操作体系/将msg标识为正源码是什么意思在运用
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null |linux常用指令| when == 0 || when < p.when) {
// 假定部队为空,或许when==0,标明需求当即实施,或许实施时刻早于链表榜首个元素时刻
//将新的msg参加mMessage链表的榜首位
msg.next = p;
mMessages = msg;
//假定处于堵塞情况,需求唤线程池创立的四种醒部队
ne线程的几种情况edWake = mBlocked;
} else {
needWake = mBlocked &&线程的几种情况amp; p.target == null && msg.isAsynchronous();线程堵塞怎样处理
Messag源码之家e prev;
//此音讯是一条延时音讯,依据音讯的when,经过for循环找到音讯的刺进点
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
br线程数越多越好吗eak;
}
if (needWake &amplinux;& p.isAsynchronous()) {
needWak线程和进程的差异是什么e = false;
}
}
//刺进音讯
msg.next = p; // i源码编辑器nvariant: p ==源码编辑器 prev.next
prev.next = msg;
}
// We can assume mPtr != 0 because mQuitting is fa源码共享网lse.
if (needWake) {
//唤醒音讯
nativeWake(mPtr);
}
}
return true;
}

mMessages是一个依照音讯实践触发时刻线程池面试题msg.when排序的链表,越往后的越晚触发。enqueueMessage办法依据新刺进音讯的wh源码共享网en,将msg刺进到链表中合适的方位。假定是及时音讯,还需求唤醒MessageQueue

咱们接着来看看nativeWake办法,nativeWake办法的源码位于frameworksbasecorejniandroid_os_Mes线程池有几种sageQueue.cpp

static void android_os_Mes源码精灵sageQueu线程池有几种e_linux体系装置nativeWake(JNIEnv* env, jclass clazz源码交易平台, jlong ptr) {
Nati线程堵塞和等候veMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
nativeMessageQueue->wake();
}

继续看NativeMes线程池的创立办法有几种sageQueue源码共享网里的wake函数。

void NativeMessag线程池的七个参数eQueue::w源码共享网ake() {
mLooper->wake();
}

它又转交给了Looper(源码方位/system/core/libutils/Looper.cpp)去处理。

void Looper::wake() {
#if DEBUG_POLL_AND_WAKE线程是什么意思
ALOGD("%p ~ wake"线程池面试题, this);
#endif
uint64_t inc = 1;
ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEv线程池原理entFd.get(), &inc, sizeof(uint64_t)));
if (nWrite != sizeof(uint64_t)) {
if (errno != EAGAIN) {
LOG_ALWAYS_FATAL("Could not write wake signal to fd %d (returned %zd): %s",
mWakelinux指令Even线程安全tFd.get(), nWrite, strerror(errno));
}
}
}线程堵塞

Looper里的wake函数很简源码编辑器略,它只是向mWakeEventFd里写入了一个 1 值。

上述的mWak线程池面试题eEventFd又是什么呢?

Looper::Looper(bool allowNlinux体系onCallbacks)
:线程安全 mAllowNonCallbacks(allowlinux常用指令NonCallbacks),
mSendingMessage(false),
mPolling(false),
mEpollRebuildRequired(false),
mNextRequestSeq(0),
mResponseIndex(0),
mNextMessageUptime(LLONG_MAX) {
mWak线程堵塞的办法有哪些eEvent源码编辑器Fd.reset(eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC));
...
}

从Looper的结构函数里可以找到答案,mWakeEventFd本线程池的七个参数质上是一个eventfd。至于什么是eventfd,这儿只能说是eventfdLinux 2.6供给的一种体系调用,它可以用来结束作业通知,更具体的内容需求各位读者自行查阅材料了。

已然有发送端,那么必定有接纳端。接纳端在哪呢?

void Looper::awoken() {
#ilinuxf DEBUG_POLL_AND_WAKE
ALOGD("%p ~ awoken", this);
#endif
uint64_t counter;
TEMP_FAILURE_RETRY(read(mWakeEventFd.get(), &counter, sizeof(uint64_t))线程是什么意思);
}

可以看到,awoken函数里的内容很简略,只是做了一个读取的动作,它并不联络读到的具体值是啥。为什么要这样规划呢,咱们得结合awoken函数在线程池的七个参数哪里调用去剖析。

alinux是什么操作体系woken函数在Lo线程堵塞和等候operpollInner函数里调用。pollInner函数里有一条语句

int eventCount = epoll_wait(mEpollFd.g线程安全et(), eventItelinux必学的60个指令ms, EPOLL_MAX_EVENTS, timeoutMillis);

它在这儿起到堵塞的效果,假定没有调用nativeWake函数,epoll_wai线程池t将一向等候写入作业,直到超时连续。

如此,便回到咱们文章一开始提出的问题了。

Looper.loolinuxp是一个死循环,拿不到需求处理的Message就会堵塞,那在UI线程中为什么不会导致ANR?

首要,咱们需求明晰一点,Handler中终究有没有阻线程是什么意思塞?

答案是有!!!那它为什么不会导致ANR呢?

这得从ANR产生的原理说起。

ANR的实质也是一个Message,这一点很要害。咱们拿前台服务的创立来举例,前台线程池服务创立时,线程池作业原理会发送一个
what值为线程堵塞情况Activity线程池有几种ManagerService.SERVICE_TIMEOUT_源码编辑器MSG的延时20s的Message,假定Service的创立 作业在上述音讯线程撕裂者的延时时刻内结束,则会移除该音讯,否则,在Handler正常收到这个音讯源码年代训练怎样样后,就linux操作体系基础知识会进行服务超时处理,即弹出ANR对话框。

为什么不会线程的几种情况ANR,现在各位读者清楚了吗?ANR音讯本身便是经过Handler去派发源码共享网的,Handler堵塞与否与ANR并没有必定联络。


咱们看了MessageQueue是怎样参加一条音讯的,接下来,咱们来看看它是怎样取出一条消线程池的七个参数息的。

Message next() {
//Linux假定音讯循环已退出线程池怎样确保线程履行次序并已被开释,则return
//假定应用程序在退出后查验重新启动looper,则可能发生这种情况
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1; // -1 onl源码编辑器y dur线程是什么意思ing first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
//将当时线程中挂起的全部Binder指令刷新到内linux常用指令核驱动程序。
//在实施可能会堵塞很长时刻的操作之前调用此函数十分有用,以确保已开释任何挂起的政策引用,
//然linux体系装置后防止进程保留政策的时刻逾越需求的时刻。
Binder.flushPeLinuxndingCommands();
}
//用于等候下一条可用音讯,运用了linux epoll机制线程堵塞怎样处理堵塞,不会占用c线程pu时刻
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// 查验寻找下一条message
final long now = SystemClock.uptimeMillis();
Messag线程池回绝战略e prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
// 被阻碍阻遏。查找部队中的下一条异步音讯
do {
prevMsg = ms线程池参数详解g;
msg = msglinux体系装置.next;
} while (msg != null && !msg.isAsynchronous());
}线程堵塞和非堵塞
if (msg != null) {线程池参数详解
if (no源码w < msg.when) {
//下一条音讯没有准备好。设置超时,以便在准备就绪时唤醒,超时时刻为下一条音讯触发时刻和当时时刻的时刻差
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integ线程堵塞是什么意思er.MAX_VALUE);
} else {
// 移出并回来链表榜首条音讯
mBlocked = false;
if (prev源码年代训练怎样样Msg != null) {
prevMs线程池面试题g.next线程堵塞情况 = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;
}
} else {
/线程/ No more messages.
nextPollTimeoutMillis = -1;
}
//处理源码编辑器完全部挂起的音讯后,当即处理退出音讯
if (mQuitting) {
dispose();
return null;
}
// 下面都是IdleHandler逻辑
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
// No i线程是什么意思dle handlers to run.  Loop and wait some more.
mBlocked = true;
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pen线程堵塞和等候dingIdleHandler线程池原理Count, 4)];
}
mPendingIdleHan线程池dlers线程堵塞是什么意思 = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// Run the idle handlers.
// We only ever reach this code block during the first iteration.
for (int i = 0; i < pendingIdle线程HandlerCount; i++) {
final IdleHandler idler = mPendingIdleHa线程池的创立办法有几种ndlers[i];
mPendingIdleHa线程池回绝战略ndlers[i] = null; // release the reference to the handler
boolean keep源码 = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
// Reset the线程池面试题 idle handler count to 0 s线程池o we do not run them again.
pendingIdleHandlerColinux必学的60个指令unt = 0;线程池有几种
// While c线程池面试题alling an idle handler, a new message could have been delivere源码年代d线程池原理
// so go back and look again线程池原理 for a pending message without waiting.
nextPollTimeoutMillis = 0;
}
}

n源码之家ext办法里首线程堵塞和非堵塞要做了三件事,(1)运用nativePollOnce堵塞线程池参数详解指定时刻,等候下一条音讯的实施。
(2)获取下一条音讯,并回来此音讯。线程堵塞和死锁 (3)假定音讯部队为空,则实施源码之家IdleHandler。

这儿有个新名词IdleHandlerIdleHa线程堵塞的处理办法nd线程池原理ler是可以在 Looper 作业循环的过程中,当呈现闲暇的时分,容许咱们实施使命的一种机制。
MessageQueue中供给了addIdleHandlerremoveIdleHandler去增加删去IdleHandler


next办法的榜首行有个ptr变量,这个ptr变量是什么意义呢?linux检查进程指令

MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}

mPtr是一个long型变量,它是在MessageQueue的结构办法中,经过nativeInit办法初始化的。

static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clalinux是什么操作体系zz) {
NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
if (!nativeMessageQueue) {
jniThrowRuntimeException(en源码资源站v, "Unablinux检查进程指令le to allo线程堵塞会占用cpu吗cate native queue");
return 0;
}
nativeMessag线程池的七个参数eQueue->incStrong(env);
return reinterpret_cast<jlong>(nativeMessageQueue);
}

可以看到,ptr的实质是对 jni层的NativeMessageQueue政策的指针的引用。


咱们要害来看下nLinuxativePollOnce办法,探寻一下Handler中的堵塞机制。nativePollOnce办法毕线程池竟调用的是Looper.cpp中的pollOnce函数。

int Looper::源码共享网pollOnce(int timeoutMilli线程池面试题s, int* outFd, int* outEvents, void** outData) {
int result = 0;
for (;;) { //一个死循环
whil线程池e (mResponseIndex < mResponses.size()) {
const Re线程堵塞sponse& response = mResponses.itemAt(mResponseIndex++);
int ident = response.request.ident;
if (ident >= 0) {
int fd = response.request.f源码年代d;
int events = response.events;
void* data = response.request.data;
#if DEBUG_POLL_AND_WA线程堵塞原因KE
ALOGD("%p ~ pollOnce - returning signalled identifier %d: "线程池面试题
"fd=%d, events=0x%x, data=%p",
this, ident, fd, events, data);
#endif
if (outFd != nullptr) *outFd = fd;
if (outEvents != nullptr) *outEvents =linux检查进程指令 events;
if (outData != nullptr) *outDatlinux体系装置a = data;
return ident;
}
}
if (result != 0) {
#if DEBUG_POLL_AND_WAKE
ALOGD("%p ~ p线程ollOnce - returning result %d", this线程撕裂者, result);
#endif
if (outFd != nullptr) *outFd = 0;
if线程堵塞的办法有哪些 (outEvents != nullptr) *outEvents = 0;
if (outData != nullptr) *outData = nullptr;
return result;
}
result = pollInner(timeoutMillis);
}
}

函数里有个关于mResponses的whi源码年代le循环,咱们从java层线程池调用的暂时不用管它,它是ndk的ha线程池原理ndler处理逻辑。咱们要害来看pollInner函数。

int Looper::pollInn线程堵塞和死锁er(int timeoutMillis) {
// 依据下一条音讯的到期时刻调整超时。
if (timeoutMillis != 0 && mN线程池的七个参数extMessageUptime != LLONG_MAX) {
nsecs_t n线程池创立的四种ow = systemTime(SY源码年代训练怎样样STEM_TIME_MONOTONIC);
int messageTimeoutMillis = toMillisecondTimeoutD线程堵塞原因elay(now, mNextMessageUptime);
if (messageTi源码年代meoutMillis >= 0
&& (timeoutMillis < 0 || messageT线程池imeoutMillis < timeoutMillis)线程池) {
timeoutMillis = messageTimeoutMillis;
}
}
// 默许触发唤醒作业,POLL_WAKE == -1
int result = POLL_WAKE;
mResponses.clelinux重启指令ar();
mRespons线程堵塞怎样处理eIndex = 0;
// We are about to idle.
mPolling = true;
struct epoll_event eventItems[EPOLL_MAX_EVENTS];
//等候写入作业,写入作业由awoken函数触发。timeoutMillis为超时时刻,0当即回来,-1一向等候
int eventCount = epoll_wait(mEpollFd.get(), eventItems, E线程池有几种POLL_MAX_EVENTS, timeoutMillis);
// No longer idling.
mPolling = false;
// Acquire lock.
mLoc源码共享网k.lock();
...
// Check for poll error.
if (eventCount < 0) {
if (errno == EINTR) {
goto Done;
}
ALOGW("Poll failed with an unexpected error: %s", strerror(errno));
//POLL_ERRO线程堵塞R == -4
result = POLL_ERROR;
goto Done;
}
// Check for poll timeout.
if (eventCount ==线程的几种情况 0) {
//POLL_TIMEOUTLinux == -3,epoll超时会走此分支
result = Plinux操作体系基础知识OLL_TIMEOUT;
goto Do线程池的创立办法有几种ne;
}
// Handle源码年代 all events.
for (int i = 0; i < eventCount; i++linux检查进程指令) {
int fd =源码编辑器 eventItem线程堵塞会占用cpu吗s[i].data.fd;
uint32_t epollEvents = eventItems[i].events;
if (fd == mWakeEventFd.get())linux体系 {
if (epollEv线程池ents & EPOLLIN) {
//将eventfd里的数值取出,无实践意义,只是为源码年代训练怎样样了清空epoll作业和eventfd里的数据
awoken();
} else {
ALOGW("Ignoring unexpected epoll events 0x%x on wake event fd.", epollEvents);
}
} else {
//不会走到此分支,忽略它
ssize_t requestIndex = mRequests.indexOfKey(fd);
if (requestIndex >= 0) {
int events = 0;
if (epo线程池有几种llEven线程和进程的差异是什么ts & EPOLLIN) events |= EV线程数越多越好吗ENT_INPUT;线程堵塞情况
if (epollEvents & EPOLLOUT) events |= EVENT_OUTPUT;
if (epollEvents & EPOLLERR) events |= EVENT_ERROR;
if (epollEvents & EPOLLHUP) events |= EVENT_HANGUP;
pushResponse(events, mRequests.valueAt(requestIndex));
} else {
ALOGW("Ignoring unexpected e线程安全poll events 0x%x on fd %d thatlinux是什么操作体系 is "
"no longer registered.", epollEvents, fd);
}
}
}
Done: ;
// 中心省掉的代码不做根究,和ndk的handler源码是什么意思结束有关
...
return result;
}

可以看到,pollInne源码共享网r函数首要的逻辑是运用epoll_wait去读取唤醒作业,它有一个最大的等候时长,其最大等候时长和下一条音讯的触发时刻有关。

需求留神一下pollInner的回来值result,它linux指令有三种情况。进入办法默许为POLL_WAKE,标明触发唤醒作业。
接下来经过对epoll_wait回来值的线程池面试题判别,它可能会变更为另两种情况。epoll_wait回来值为0,标明epoll_wait因超时而线程堵塞的办法有哪些结束等候线程池作业原理,result值设为POLL_TIMEOUT;epoll_wait回来值为-1,标明epoll_wait因体系连续等原因而结束等候,resu线程池创立的四种lt值设为POLL_ERROR。但不论result值设为源码交易平台哪一个,线程堵塞原因都会导致pollOnce退出死循环,然代码流程回到java层的next办法中,去获得下一个Message政策。

因而,nativePo线程池llOnce简略意义上的了解,它便是一个阻断器,可以将当时线程堵塞,直到超时或许因需当即实施的linux新音讯入队才结束堵塞。

各位读者,看到这儿,咱们再回过头去想想文章的榜首个问题该怎样答复吧。

Looper

Handler 机制中,咱们还剩终究线程数越多越好吗一个一个模块源码本钱没有剖析———— Looper。咱们先从官方界说来看起:

* Class used to run a message loop for a thread线程数越多越好吗.  Threads by default do
* not have a message loop associated with them; to create one, call
* {@link #p源码交易平台repare} in the threa线程堵塞的办法有哪些d that is to run thelinux必学的60个指令 loop, and then
* {@link #loop} to hav线程池有几种e it proce线程池原理ss messages until the loop is stopped.

归纳一下:

Looper是一个用线程池作业原理于在线程中循环遍历音讯的类。默许情况下,线程没有与之相关的音讯循环;假定要创立一个,请在作业Looper的线程中调用Looper.prepare(),然后运用Looper.loop()让它处理音讯直到循环连续。

上面的界说线程池提到了两个比较要害的办法,咱们一个一个来看。

Looper.pre线程池的七个参数pare()线程池有几种

privatlinux是什么操作体系e static void prepare(bool线程堵塞怎样处理ean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set线程堵塞的办法有哪些(ne线程堵塞原因w Looper(quitAllowed));
}

prepare的办法内容十分简略,创立一个Looper政策,并把它放到sThreadLocal里,其间sThreadLocal是一个ThreadLocal类。

ThreadLocal类又是什么呢?

多线程拜访同一个同享变量的时分简略呈现并发问题,特别是多个线程对一个变量进行写入的时分,为了确保线程安全,一般运用者在拜访同享变量的时分需求进行额外的同步措施才调确保线程安全性。ThreadLocal是除了加锁这种同步办法linux体系之外的一种确保一种逃避多linux操作体系基础知识线程拜访呈现线程不安全的办法,当咱们在创立一个变量线程池面试题后,假定每源码编辑器个线程对其进行拜访的时分线程池面试题拜访的都是线程自己的变量,这样就不会存在线程不安全问题。

因而,运用ThreadLocal可以确保不同线程的Looper政策都有一个独立的副本,它们相互独立,互不烦扰。


Loolinux重启指令per.looper()

public static线程堵塞的处理办法 void loop() {
//获取当时线程的Looper政策
f源码本钱inal Looper me = myLooper()linux体系;
if (me == null) {
throw线程数越多越好吗 new RuntimeException("No Looper; Looper.pre线程堵塞的办法有哪些pare() wasn't called on this t线程堵塞hread.");
}
//获取与Looper相关的messagequ线程堵塞和非堵塞eue
final MessageQueue queue = me.mQueue;
/源码/ Make sure the identity of th线程堵塞和死锁is thread is that of the loc线程池作业原理al p线程堵塞情况rocess,
//线程堵塞怎样处理 and k线程池回绝战略eep track of what that identity token actually ilinux是什么操作体系s.
Binlinux指令der.clearCallingIdentity();
final llinux检查进程指令ong ident = Binder.clearCallingIdentity();
// Allow overriding a threshold with a system prop. e.源码年代训练怎样样g.
// adb sh线程堵塞会占用cpu吗ell 'setprop log.looper.1000.main.slow 1 && stop &am源码是什么意思p;& start'
final int thresholdOverride =
SystemPrope源码本钱rties.getInt("log.lo线程堵塞和死锁oper."
+ Process.myUid() + "."
+ Thread.currentThre源码资源站ad().getName()
+ ".slow", 0);
boolean slowDeliveryDetected = false;
for (;;) {
//进入死循线程堵塞是什么意思环,不断线程池回绝战略去从MessageQueue中去拉取Message
Message msg = queue.源码共享网next(); // next办法咱们现已在MessageQueue中做了剖析
if (msg == null) {
// No message indicates that t线程池怎样确保线程履行次序he message queue is quitting.
return;
}
// Make sure the observer won't change while processin线程堵塞的办法有哪些g a transaction.
finalinux体系l Observer observer = sObserver;
...
final long线程池 dispatchStart = needStartTime ? SyLinuxstemClock.uptimeMillis线程池作业原理() : 0;
final long dispatchEnd;
Object t线程堵塞原因oken = null;
if (observer != null) {
token = observer.messageDispatchStarting();
}
long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);
try {
//留神这儿,msg.target是一个handler政策,这个办法终究调用了handler的dispatchMessage
//去做音讯分发
msg.target.dispatchMessage(msg);
if (observer != null) {
observer.messageDispatched(linux是什么操作体系token, msg线程);
}
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} catch (Exception exception) {
if (observer != null) {
obse源码精灵rver.dispatchingThrewException(token, msg, exception);
}
throw exception;
} finally {
ThreadLocalWorkSource.restore(origWorkSource);
if (t线程撕裂者raceTag != 0) {
Trace.traceEnd(linux是什么操作体系traceTag);
}
}线程池作业原理
//收回Message,上文中有做过剖析
msg.recycleUnchecked();
}
}

loop办法首要线程池创立的四种的作业是:建立一个死循环,不断的经过调用MessageQue线程池回绝战略ue中的next办法获取下一个音讯,并终究经过获得的音讯线程池原理相关的handler去结束音讯的分发。

总结

终究,咱们再来理一理 HandlerMessageMessageQueueLooper线程池原理四者的联络和责任。

  • Handler : 音讯分发的管理者。担源码精灵任获取音讯、封装音讯、派发音讯以及处理音讯。
  • Message :音讯的载体类。
  • MessageQueue :音讯的容器。担任按音讯的触发时刻对音讯入队出队,以及在合适的时刻唤醒或休眠音讯部队。
  • Looper : 音讯分发的实施者。担任从音讯部队中拉去音讯并交给handler去实施。

为了更好的了解它们的联络,拿现实生活中的场景来举个比如:

Handler是快递员,担任收快递,取快递,线程池怎样确保线程履行次序查快递以及退回快递。
Message是快递包裹,message的target特征便是收件地址,而延时音讯便是收件人预订了派送时刻,
期望在指定的时刻linux操作体系基础知识上门派送。
MessageQueue是菜源码之家鸟驿站,要对快递进行收拾并摆放在合适的方位。
Looper是一个24小时不歇息的本钱家,他总是不断的在看菜鸟驿站有没有需求派送的快递,一有快递就立马取
出然后压榨快递员去派送。

终究,咱们用一张四者之间的流程图来结束整篇文章:

你真的了解Handler吗?

发表评论

提供最优质的资源集合

立即查看 了解详情