概述
Android framework 有大量使用synchronized/lock(wait/notify/notifyAll),最近有看到一些文件的lock机制,Google有改进,方法很巧妙,另外自己理解的也不是很透彻,希望能得到同友的指点。
首先抽象出Android中使用object lock的一个最典型的例子(具体的可以参考AOSP文件UsimPhoneBookManager.java)。
// 继承Handler,在Handler收到event的时候一般需要进行lock的notify的动作
public class AndroidSimpleLockManager extends Handler {
// 定义一个Object lock,下面的大部分逻辑都是操作mLock
private Object mLock = new Object();
// 定义一个EVENT,Hanlder需要处理
private static final int EVENT_LOAD_SIM_INFO_DONE = 1;
// 定义一个function,这个函数需要通过RIL.java的HIDL接口读取SIM卡的一些info
// 这个函数执行在非main thread
public void readInfoFromSIM() {
synchronized (mLock) {
mCi.loadInfoFromSIM(obtainMessage(EVENT_LOAD_SIM_INFO_DONE))
try {
mLock.wait();
} catch (InterruptedException e) {
}
}
}
@Override
public void handleMessage(Message msg) { // main thread执行
AsynResult ar;
switch(msg.what) {
case EVENT_LOAD_SIM_INFO_DONE:
ar = (AsyncResult) msg.obj;
if (ar.exception == null) {
// 存储读取到的内容
saveResult(ar.result);
}
synchronized(mLock) {
mLock.notify();
}
break;
}
}
}
最常用的基本就是上面的用法,定义一个Object作为lock,读取内容的时候或者做重要的事情需要异步完成的时候,就call一个function,之后进行wait(),等操作完成后,main thread收到event,进行notify,利用了message Handler实现的。上面的这种用法非常常见,但是有一个缺点,就是如果有多个类似readInfoFromSIM的功能函数,如果可以从不同的sub thread的call过来,那么就是有多个thread处于wait的状态,但是notify的时候,是任意一个wait的thread被唤醒,可能不是对应的event,这就导致期望的事情还没有发生,但是提前结束了,读取的内容不是期望的。如果使用notify all也不能解决这个问题。
为了解决上面出现的问题,Android里面有其他lock的使用可以解决这个问题,基本思想就是将识别功能的参数传入Message,等收到event的时候设置message里面存现的变量值,之后进行notify,被唤醒的thread会check这个变量,是否值发生变化了,如果没有发生变化,继续外套。如果发生变化了,就释放锁。具体可以参考下面的sample(具体可以参考Android P 之前的IccPhoneBookInterfaceManager.java)
public class AndroidHigHerLockManager {
// 定义一个object,作为lock
private final Object mLock = new Object();
// 定义一个event,还是收到event进行notify
private static final int EVENT_READ_DONE = 1;
// 直接定义一个Handler,外部类没有继承Handler
protected Handler mBaseHandler = new Handler () {
@Override
public void handleMessage() {
AsyncResult ar;
switch(msg.what) {
case EVENT_READ_DONE:
ar = (AsyncResult) msg.obj;
synchronized(mLock) {
if (ar.exception == NULL) {
saveResult(ar.result);
}
// lock notify
notifyPending(ar);
}
break;
}
}
private void notifyPending(AsyncResult ar) {
if (ar.userObj != null) {
AtomicBoolean status = () ar.userObj;
// 设置status为true,被唤醒的thread会检查status的值
status.set(true);
}
mLock.notifyAll();//注意这里是notifyAll,不是notify
}
};
protected void waitForResult(AtomicBoolean status) {
// 使用的是while条件,status.get如果是false,一直处于wait;
// 如果被notifyAll唤醒,发现status.get()仍然是false,也就是不是
// 期望的event,那么继续wait,一直等到自己的event 设置status的值和notify
while (!status.get()) {
try {
mLock.wait()
} catch (InterruptedException e) {
// error handling
}
}
}
public void readInfoFromModem() {
synchronized(mLock) {
// 下面初始化值是false
AtomicBoolean status = new AtomicBoolean(false);
Message response = mBaseHandler.obtainMessage(EVENT_READ_DONE, status);
mCi.LoadInfoFromModem(response);
waitForResult(Status);
}
}
}
上面这个例子可以和第一个例子做一下比较,如果有多个类似readInfoFromModem的函数,有多个thread处于wait状态,如果有一个event到来,那么设置Message里面的status的值之后进行notifyAll,被唤醒的thread会去检查status的变量是否是true,如果是true,就释放锁,说明当前event就是自己期望的event,如果还是false,说明这个event不是自己预期的等待事件,所以仍然继续wait,等待下一次被唤醒,直到自己期望的event到来。这样就不会乱了。当然这个机制依赖于AtomicBoolean。这个和第一个simple lock有了很大的进步,但是有一个缺点就是只有一个mLock,多个线程操作的时候就会出现线程不断的唤醒和wait状态进行切换,损耗CPU。java的线程是映射到操作系统原生线程之上的,如果要阻塞或唤醒一个线程就需要操作系统介入,需要在户态与核心态之间切换,这种切换会消耗大量的系统资源,因为用户态与内核态都有各自专用的内存空间,专用的寄存器等,用户态切换至内核态需要传递给许多变量、参数给内核,内核也需要保护好用户态在切换时的一些寄存器值、变量等,以便内核态调用结束后切换回用户态继续工作。如果线程状态切换是一个高频操作时,这将会消耗很多CPU处理时间(From https://segmentfault.com/a/1190000013512810?utm_source=tag-newest)。如果想避免这种情况,最好的方法就是一个线程一个锁,而不是多个线程共用一个锁。那也就是说锁是动态创建,每一个thread一个新锁,我们看一下Android是如何实现的。每个thread一个锁的实现的基本思想和第二个例子很相近,还是利用message Handler,第二种Message传入的AtomicBoolean变量,现在传入的是一个新的对象,lock wait的就是这个新对象,event收到后,解析message里面的对象,进行notify,这样被唤醒的线程就是前面wait的线程了,而其他线程就不会被唤醒,因为 thread wait的对象不同。具体可以参考下面的sample (具体可以参考Android Q 的IccPhoneBookInterfaceManager.java)
public class AndroidThirdLockManager {
// 不用定义mLock
// 定义一个event,还是收到event进行notify
private static final int EVENT_READ_DONE = 1;
// 定义了一个Request,后面的lock就是Request的类型的
private static final class Request {
// Request的element也是AtomicBoolean,请和第二例子的作用进行比对
public AtomicBoolean mStatus = new AtomicBoolean(false);
}
// 直接定义一个Handler,外部类没有继承Handler
protected Handler mBaseHandler = new Handler () {
@Override
public void handleMessage() { //在main thread执行
AsyncResult ar = (AsyncResult) msg.obj;
Request request = (Request) ar.userObj;
switch(msg.what) {
case EVENT_READ_DONE:
if (ar.exception == NULL) {
saveResult(ar.result);
}
// lock notify
notifyPending(request);
break;
}
}
// 注意和第二例子进行比对
private void notifyPending(Request request) {
if (request != null) {
synchronized (request) {
request.mStatus.set(true);
request.notifyAll();
}
}
}
};
private void waitForResult(Request request) {
synchronized (request) {
while (!request.mstatus.get()) {
try {
request.wait();
} catch (InterruptedException e) {
// error
}
}
}
}
public void readInfoFromModem() {
Request request = new Request();
synchronized(request) {
// 下面初始化值是false
Message response = mBaseHandler.obtainMessage(EVENT_READ_DONE, request);
mCi.LoadInfoFromModem(response);
waitForResult(request);
}
}
}
请仔细比对但三个例子和第二例子的区别,函数名都相同,实现也很类似,只是传入Message的object编程了lock本身,这样使得event notify的时候只有生成event的thread被唤醒,而其他线程不会被唤醒,因为每次wait的object lock
都是new出来的,不会导致重复,这样就实现了每个thread或者每次调用函数lock都是新生成的,不会重复,这样子就实现了每个线程一把锁,这样就不会出现第二例子找那个竞争一把锁的情形,线程也不会被随意的唤醒和再次wait的状况,
第三个例子和第二个例子虽然差别不是很大,但是这个idea非常重要,效果也非常明显。当然整个实现也离不开Message Hanlder和AtomicBoolean。
对于lock的使用是多线程的重点,想要用好还需要理解lock的机制,wait/notify/notifyAll的意义,唤醒的意思,等待锁的意义。只有了解了这些,再来理解code中的代码,为什么Google要改成这样,好处是什么,就比较明了了。另外这里并不是指第三种好于第二种,第二种好于第一个sample,不同的情形使用不同的方案可能才是最佳的。通过比较不同的实现方式,对于理解lock有一个比较好的帮助。上面也是我自己的理解,如果有不对的地方,请同道指教。
最后
以上就是动听冥王星为你收集整理的Android framework lock的使用总结(wait/notify/notifyAll,lock的不同情景使用)的全部内容,希望文章能够帮你解决Android framework lock的使用总结(wait/notify/notifyAll,lock的不同情景使用)所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复