wait、notify方法介绍
wait和notify是Java中的两个方法,可以用于线程间的协作,最典型的应用就是生产和消费者模式。不过,没有真正用过的同学可能会认为它们是Thread或者Runnable的方法,其实不然,它们是Object父类的方法。
先看下Object.java中wait和notify方法的定义:
wait方法:
public final void wait(long millis) throws InterruptedException {
wait(millis, 0);
}
public final native void wait(long millis, int nanos) throws InterruptedException;
public final native void wait() throws InterruptedException;
notify方法:
public final native void notify();
public final native void notifyAll();
可以看出,wait和notify都是native方法,具体实现可能涉及效率考虑,所以通过native代码去实现了。
Launcher中的应用场景分析
以Launcher启动时,加载数据和绑定界面为例,这里通过wait和notify的使用,保证了UI线程优先执行。
以下是LauncherModel的startLoaderForResults方法,调用后会启动工作线程开始加载和绑定流程
public void startLoaderForResults(LoaderResults results) {
synchronized (mLock) {
stopLoader();
mLoaderTask = new LoaderTask(mApp, mBgAllAppsList, sBgDataModel, results);
runOnWorkerThread(mLoaderTask);
}
}
LoaderTask实现了Runnalbe接口,它的run方法中,就是具体的实现了。从注释可以看到,分了好多步骤去加载数据和绑定界面,执行的任务还是挺繁重的,为了简洁明了的说明问题,这里去掉了后面部分代码,只保留了loadWorkspace、bindWorkspace、loadAllApps和bindAllApps实现代码。
public void run() {
synchronized (this) {
// Skip fast if we are already stopped.
if (mStopped) {
return;
}
}
TraceHelper.beginSection(TAG);
try (LauncherModel.LoaderTransaction transaction = mApp.getModel().beginLoader(this)) {
TraceHelper.partitionSection(TAG, "step 1.1: loading workspace");
loadWorkspace();
verifyNotStopped();
TraceHelper.partitionSection(TAG, "step 1.2: bind workspace workspace");
mResults.bindWorkspace();
// Notify the installer packages of packages with active installs on the first screen.
TraceHelper.partitionSection(TAG, "step 1.3: send first screen broadcast");
sendFirstScreenActiveInstallsBroadcast();
// Take a break
TraceHelper.partitionSection(TAG, "step 1 completed, wait for idle");
waitForIdle();
verifyNotStopped();
// second step
TraceHelper.partitionSection(TAG, "step 2.1: loading all apps");
loadAllApps();
TraceHelper.partitionSection(TAG, "step 2.2: Binding all apps");
verifyNotStopped();
mResults.bindAllApps();
......
transaction.commit();
} catch (CancellationException e) {
// Loader stopped, ignore
TraceHelper.partitionSection(TAG, "Cancelled");
}
TraceHelper.endSection(TAG);
}
run方法运行在工作线程,mResults.bindWorkspace()方法实现其实是启动的UI线程在绑定界面,虽然run方法是工作线程,可以继续做自己的事情,而且正常不会导致ANR,但是在主线程高负荷刷新和渲染界面的时候,如果存在后台线程也在忙碌运转的话,主线程多少还是会受到影响的,CPU时间片肯定会减少,界面加载时间就会变长,这不是我们愿意看到的。
在Launcher这样的应用中,更是不能容忍,看看上面代码中的注释‘Take a break’,在继续loadAllApps之前,工作线程做了一个暂停,为什么要这样做呢?这其实是在给bindWorkspace让路,延迟loadAllApps,实现线程暂停的就是waitForIdle()方法。
看下waitForIdle()的实现,用到了while循环,看起来还是在做事情,没有wait之类的方法暂停线程,但是调用了LooperIdleLock的awaitLocked(1000)方法
protected synchronized void waitForIdle() {
// Wait until the either we're stopped or the other threads are done.
// This way we don't start loading all apps until the workspace has settled
// down.
LooperIdleLock idleLock = mResults.newIdleLock(this);
// Just in case mFlushingWorkerThread changes but we aren't woken up,
// wait no longer than 1sec at a time
while (!mStopped && idleLock.awaitLocked(1000));
}
继续往下看LooperIdleLock的的实现
public class LooperIdleLock implements MessageQueue.IdleHandler, Runnable {
private final Object mLock;
private boolean mIsLocked;
public LooperIdleLock(Object lock, Looper looper) {
mLock = lock;
mIsLocked = true;
if (Utilities.ATLEAST_MARSHMALLOW) {
looper.getQueue().addIdleHandler(this);
} else {
// Looper.myQueue() only gives the current queue. Move the execution to the UI thread
// so that the IdleHandler is attached to the correct message queue.
new LooperExecutor(looper).execute(this);
}
}
@Override
public void run() {
Looper.myQueue().addIdleHandler(this);
}
@Override
public boolean queueIdle() {
synchronized (mLock) {
mIsLocked = false;
mLock.notify();
}
return false;
}
public boolean awaitLocked(long ms) {
if (mIsLocked) {
try {
// Just in case mFlushingWorkerThread changes but we aren't woken up,
// wait no longer than 1sec at a time
mLock.wait(ms);
} catch (InterruptedException ex) {
// Ignore
}
}
return mIsLocked;
}
}
awaitLocked方法中调用了mLock.wait(ms),每次调用一次会等待1秒钟,如果while循环判断mIsLocked为true,就会一直等待下去,直到外部线程调用了mLock的notify方法。
全局搜索了launcher的代码后,发现只有两个地方调用了notify方法:
一处是LooperIdleLock的queueIdle方法:
@Override
public boolean queueIdle() {
synchronized (mLock) {
mIsLocked = false;
mLock.notify();
}
return false;
}
queueIdle方法是MessageQueue.IdleHandler接口的方法,从上面LooperIdleLock的代码实现可以看出,LooperIdleLock实现了这个接口
public LooperIdleLock(Object lock, Looper looper) {
mLock = lock;
mIsLocked = true;
if (Utilities.ATLEAST_MARSHMALLOW) {
looper.getQueue().addIdleHandler(this);
} else {
// Looper.myQueue() only gives the current queue. Move the execution to the UI thread
// so that the IdleHandler is attached to the correct message queue.
new LooperExecutor(looper).execute(this);
}
}
LooperIdleLock在构造函数中,通过looper.getQueue().addIdleHandler(this)往主线程消息队列中注册了这个接口,主线程空闲时,会回调queueIdle方法,进而通知work线程,不用等待了。
另一处是LoaderTask的stopLocked方法:
public synchronized void stopLocked() {
mStopped = true;
this.notify();
}
这个方法是用来停止加载和绑定流程的,结束流程的同时,当然也要结束工作线程中正在执行的任务,停止等待,notify就是用于唤醒线程,让它结束任务。
总结
不知不觉对于Launcher中wait和notify的应用已经分析完毕,应用场景即,在UI线程bindWorkspace时候,通过wait停止线程,等Workspace绑定结束再去loadAllApps,而notify在Workspace绑定结束后会被调用,从而唤醒线程继续执行loadAllApps,这个应用保证了UI线程优先执行。
通观整个AOSP launcher也就这一个地方用到了wait和notify,可见平时我们基本用不到,只有在特定的场景才会遇到。