最近在阅读HanlerThread源码时在getLooper中有wait函数,在run中用到了notifyall函数。于是就思考这两个函数的作用是什么?为什么需要在这里添加这两个函数?经过一番研究终于理解了,这边做一个总结。
/**
* This method returns the Looper associated with this thread. If this thread not been started
* or for any reason isAlive() returns false, this method will return null. If this thread
* has been started, this method will block until the looper has been initialized.
* @return The looper.
*/
public Looper getLooper() {
if (!isAlive()) {
return null;
}
// If the thread has been started, wait until the looper has been created.
synchronized (this) {
while (isAlive() && mLooper == null) {
try {
wait();
} catch (InterruptedException e) {
}
}
}
return mLooper;
}
@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
从消费者生产者模式说起
为了理解wait/notify/notifyall的作用和关系,首先通过消费者生产者设计模式为例来介绍。什么是生产者消费者?简单来说就是为了保持供需平衡的一种手段。生产者在发现仓库里面没有货物了再去生产,当生产到一定数量之后就停止生产,并告诉消费者有货了你可以过来消费了;而消费者会不断从仓库中消费货物,如果没有了就等待,并告诉生产者快去生产货物。这样做的好处就是货物不会堆积,保证供需平衡。经典的生产者消费者模式代码如下所示:
// 生产者
public class Producer implements Runnable {
List<Integer> products;
public Producer(List<Integer> products) {
this.products = products;
}
@Override
public void run() {
while (true) {
produce();
}
}
private void produce() {
synchronized (products) {
try {
// 当仓库产品数量到达1个时停止生产。
// 调用wait()当前线程释放锁,并进入等待池队列
while (products.size() == 1) {
products.wait();
}
Thread.sleep(1000);
cache.add(1);
// 通知消费者消费产品。
cache.notify();
}
catch (Exception e) {
e.printStackTrace();
}
}
}
}
// 消费者
public class Consumer implements Runnable {
List<Integer> products;
public Consumer(List<Integer> products) {
this.products = products;
}
@Override
public void run() {
while (true) {
consume();
}
}
private void consume() {
synchronized (products) {
try {
// 当仓库为空时,停止消费。
// 调用wait()当前线程释放锁,并进入等待池队列
while (products.isEmpty()) {
products.wait();
}
cache.remove(0);
// 通知生产者生产产品
cache.notify();
}
catch (Exception e) {
e.printStackTrace();
}
}
}
}
在结合生产者消费者模式的代码分析之前先介绍一下等待池和锁池。
等待池:当一个线程调用某对象的wait方法,该线程就会释放该对象的锁,并进入该对象的等待池队列。
锁池:当一个线程已经拥有一个对象的锁时,其他线程想要进入该对象的synchronized的方法,就必须先获得这个对象的锁,但是该对象的锁已经被线程拥有,因此其他对象就会进入该对象的锁池。
生产者和消费者各自开启一个线程进行处理,当调用wait时,其实是将当前线程的锁释放,并进入该锁的等待池队列。而调用notify可以将该对象的等待池队列中随机一个线程唤醒进入锁池,去竞争该对象的锁。而调用notifyall可以将该对象的等待池队列中所有线程都移入该对象的锁池,去竞争该对象的锁。这里注意到一种可能引起死锁的情况:
假如有一个生产者P1和两个消费者C1 C2
1 消费者C1 C2发现仓库为空,都进入等待池。
2 P1 生产一个产品后,也进入等待池,并唤醒等待池中的C2,C2进入锁池去竞争该对象的锁。
3 C2 竞争到该锁,并消费了产品再次进入等待池,并随机唤醒一个等待池的线程,假如唤醒的是C1。
4 C1检测到当前仓库为空,直接进入等待池。这时候P1 C1 C2都在等待池中,没有人来唤醒他们,就造成了死锁。
假如用notifyAll就不会出现这种情况。因为如果使用notifyall,在第三步中C2会把C1和P1一起唤醒,即便C1检测到当前仓库为空,P1也会开始生产产品,不会造成死锁。
HandlerThread中wait和notifyall的使用
理解了wait/notify/notifyall的作用之后,再回过头来看HandlerThread中为什么在run函数中需要用到notifyall。
首先我们来看getHandlerLoop函数。首先判断这个looper所在线程是否启动,如果启动则进入同步块。在同步块中判断如果looper还没有创建成功,就将该线程移入等待池.直到run中得到looper之后调用notifyall唤醒该线程,从而得到looper.因此这里使用wait/notifyall来解决同步问题,保证只有线程创建成功并且looper创建成功才能得到looper.
网友评论