Android framework lock的使用总结(wait/notify/notifyAll,lock的不同情景使用)

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/diangangqin/article/details/100712157

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有一个比较好的帮助。上面也是我自己的理解,如果有不对的地方,请同道指教。

猜你喜欢

转载自blog.csdn.net/diangangqin/article/details/100712157