Android提供了两个封装好的同步类,它们是Mutex和Condition。这是重量级的同步技术,一般内核都会有对应的支持。另外,OS还提供了简单的原子操作,这些也算是同步技术中的一种。下面分别来介绍这三种东西。
1. 互斥类—Mutex
Mutex是互斥类,用于多线程访问同一个资源的时候,保证一次只有一个线程能访问该资源。在《Windows核心编程》①一书中,对于这种互斥访问有一个很形象的比喻:想象你在飞机上如厕,这时卫生间的信息牌上显示“有人”,你必须等里面的人出来后才可进去。这就是互斥的含义。
下面来看Mutex的实现方式,它们都很简单。
(1)Mutex介绍
其代码如下所示:
[-->Thread.h::Mutex的声明和实现]
inline Mutex::Mutex(int type, const char* name) {
    if (type == SHARED) {
       //type如果是SHARED,则表明这个Mutex支持跨进程的线程同步。
      //以后我们在Audio系统和Surface系统中会经常见到这种用法。
        pthread_mutexattr_t attr;
        pthread_mutexattr_init(&attr);
        pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);
        pthread_mutex_init(&mMutex, &attr);
        pthread_mutexattr_destroy(&attr);
    } else {
        pthread_mutex_init(&mMutex, NULL);
    }
}
inline Mutex::~Mutex() {
    pthread_mutex_destroy(&mMutex);
}
inline status_t Mutex::lock() {
    return -pthread_mutex_lock(&mMutex);
}
inline void Mutex::unlock() {
    pthread_mutex_unlock(&mMutex);
}
inline status_t Mutex::tryLock() {
    return -pthread_mutex_trylock(&mMutex);
}
关于Mutex的使用,除了初始化外,最重要的是lock和unlock函数的使用,它们的用法如下:
 要想独占卫生间,必须先调用Mutex的lock函数。这样,这个区域就被锁住了。如果这块区域之前已被别人锁住,lock函数则会等待,直到可以进入这块区域为止。系统保证一次只有一个线程能lock成功。
 当你“方便”完毕,记得调用Mutex的unlock以释放互斥区域。这样,其他人的lock才可以成功返回。
 另外,Mutex还提供了一个trylock函数,该函数只是尝试去锁住该区域,使用者需要根据trylock的返回值来判断是否成功锁住了该区域。
注意 以上这些内容都和Raw API有关,不了解它的读者可自行学习相关知识。在Android系统中,多线程也是常见和重要的编程手段,务必请大家重视。
Mutex类确实比Raw API方便好用,不过还是稍显麻烦。
(2)AutoLock介绍
AutoLock类是定义在Mutex内部的一个类,它其实是一帮“懒人”搞出来的,为什么这么说呢?先来看看使用Mutex有多麻烦:
 显示调用Mutex的lock。
 在某个时候记住要调用该Mutex的unlock。
以上这些操作都必须一一对应,否则会出现“死锁”!在有些代码中,如果判断分支特别多,你会发现unlock这句代码被写得比比皆是,如果稍有不慎,在某处就会忘了写它。有什么好办法能解决这个问题吗?终于有人想出来一个好办法,就是充分利用了C++的构造和析构函数,只需看一看AutoLock的定义就会明白。代码如下所示: 
[-->Thread.h Mutex::Autolock声明和实现]
    class Autolock {
    public:
        //构造的时候调用lock。
        inline Autolock(Mutex& mutex) : mLock(mutex)  { mLock.lock(); }
        inline Autolock(Mutex* mutex) : mLock(*mutex) { mLock.lock(); }
        //析构的时候调用unlock。
        inline ~Autolock() { mLock.unlock(); }
    private:
        Mutex& mLock;
    };
AutoLock的用法很简单:
 先定义一个Mutex,如 Mutex xlock。
 在使用xlock的地方,定义一个AutoLock,如 AutoLock autoLock(xlock)。
由于C++对象的构造和析构函数都是自动被调用的,所以在AutoLock的生命周期内,xlock的lock和unlock也就自动被调用了,这样就省去了重复书写unlock的麻烦,而且lock和unlock的调用肯定是一一对应的,这样就绝对不会出错。
2. 条件类—Condition
多线程同步中的条件类对应的是下面这种使用场景:
线程A做初始化工作,而其他线程比如线程B、C必须等到初始化工作完后才能工作,即线程B、C在等待一个条件,我们称B、C为等待者。
当线程A完成初始化工作时,会触发这个条件,那么等待者B、C就会被唤醒。触发这个条件的A就是触发者。
上面的使用场景非常形象,而且条件类提供的函数也非常形象,它的代码如下所示:
[-->Thread.h:: Condition的声明和实现]
class Condition {
public:
    enum {
        PRIVATE = 0,
        SHARED = 1
    };


    Condition();
    Condition(int type);//如果type是SHARED,表示支持跨进程的条件同步
    ~Condition();
    //线程B和C等待事件,wait这个名字是不是很形象呢?
    status_t wait(Mutex& mutex);
  //线程B和C的超时等待,B和C可以指定等待时间,当超过这个时间,条件却还不满足,则退出等待。
    status_t waitRelative(Mutex& mutex, nsecs_t reltime);
    //触发者A用来通知条件已经满足,但是B和C只有一个会被唤醒。
    void signal();
    //触发者A用来通知条件已经满足,所有等待者都会被唤醒。
    void broadcast();


private:
#if defined(HAVE_PTHREADS)
    pthread_cond_t mCond;
#else
    void*   mState;
#endif
}
声明很简单,定义也很简单,代码如下所示:
inline Condition::Condition() {
    pthread_cond_init(&mCond, NULL);
}
inline Condition::Condition(int type) {
    if (type == SHARED) {//设置跨进程的同步支持。
        pthread_condattr_t attr;
        pthread_condattr_init(&attr);
        pthread_condattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);
        pthread_cond_init(&mCond, &attr);
        pthread_condattr_destroy(&attr);
    } else {
        pthread_cond_init(&mCond, NULL);
    }
}
inline Condition::~Condition() {
    pthread_cond_destroy(&mCond);
}
inline status_t Condition::wait(Mutex& mutex) {
    return -pthread_cond_wait(&mCond, &mutex.mMutex);
}
inline status_t Condition::waitRelative(Mutex& mutex, nsecs_t reltime) {
#if defined(HAVE_PTHREAD_COND_TIMEDWAIT_RELATIVE)
    struct timespec ts;
    ts.tv_sec  = reltime/1000000000;
    ts.tv_nsec = reltime%1000000000;
    return -pthread_cond_timedwait_relative_np(&mCond, &mutex.mMutex, &ts);
     ...... //有些系统没有实现POSIX的相关函数,所以不同的系统需要调用不同的函数。
#endif 
}
inline void Condition::signal() {
    pthread_cond_signal(&mCond);
}
inline void Condition::broadcast() {
    pthread_cond_broadcast(&mCond);
}
可以看出,Condition的实现全是凭借调用了Raw API的pthread_cond_xxx函数。这里要重点说明的是,Condition类必须配合Mutex来使用。什么意思?
在上面的代码中,不论是wait、waitRelative、signal还是broadcast的调用,都放在一个Mutex的lock和unlock范围中,尤其是wait和waitRelative函数的调用,这是强制性的。
来看一个实际的例子,加深一下对Condition类和Mutex类的印象。这个例子是Thread类的requestExitAndWait,目的是等待工作线程退出,代码如下所示:
[-->Thread.cpp]
status_t Thread::requestExitAndWait()
{
    ......
   requestExit(); //设置退出变量mExitPending为true。
    Mutex::Autolock _l(mLock);//使用Autolock,mLock被锁住。
    while (mRunning == true) {
    /*
     条件变量的等待,这里为什么要通过while循环来反复检测mRunning?
     因为某些时候即使条件类没有被触发,wait也会返回。关于这个问题,强烈建议读者阅读
     前面推荐的《Programming with POSIX Thread》一书。
   */
      mThreadExitedCondition.wait(mLock); 
    }


    mExitPending = false;
   //退出前,局部变量Mutex::Autolock _l的析构会被调用,unlock也就会被自动调用。
    return mStatus; 
}
那么,什么时候会触发这个条件呢?是在工作线程退出前。其代码如下所示:
[-->Thread.cpp]
int Thread::_threadLoop(void* user)
{
    Thread* const self = static_cast(user);
    sp strong(self->mHoldSelf);
    wp weak(strong);
    self->mHoldSelf.clear();


    do {
          ......  
          result = self->threadLoop();//调用子类的threadLoop函数。
           ......
         //如果mExitPending为true,则退出。
        if (result == false || self->mExitPending) {
            self->mExitPending = true;
            //退出前触发条件变量,唤醒等待者。
            self->mLock.lock();//lock锁住。
            //mRunning的修改位于锁的保护中。如果你阅读了前面推荐的书,这里也就不难理解了。
            self->mRunning = false; 
            self->mThreadExitedCondition.broadcast();
            self->mLock.unlock();//释放锁。
            break;//退出循环,此后该线程函数会退出。
        }
        ......
    } while(strong != 0);
    
    return 0;
}
关于Android多线程的同步类,暂时介绍到此吧。当然,这些类背后所隐含的知识及技术是读者需要倍加重视的。
提示 希望我们能养成一种由点及面的学习方法。以我们的同步类为例,假设你是第一次接触多线程编程,也学会了如何使用Mutex和Condition这两个类,不妨以这两个类代码中所传递的知识作为切入点,把和多线程相关的所有知识(这个知识不仅仅是函数的使用,还包括多线程的原理,多线程的编程模型,甚至是现在很热门的并行多核编程)普遍了解一下。只有深刻理解并掌握了原理等基础和框架性的知识后,才能以不变应万变,才能做到游刃有余。

更多相关文章

  1. 面试时总被面试官挖的Android基础题掉坑里?整理了26道面试题 ,牢固
  2. Android多线程的实现方式及使用场景
  3. Android(安卓)ANR问题原因分析
  4. AsyncTask的用法总结
  5. Android应用开发之(让你的应用向后兼容)
  6. AndroLua, Luajava初步探究
  7. Android中LogCat输出日志的自定义
  8. android消息处理机制原理解析
  9. Android(安卓)HAL是如何被调用的

随机推荐

  1. android studio 3.6.0 绑定视图新特性的
  2. Android XML 解析
  3. android ActivityManagerService服务详解
  4. Eclipse Android SDK Manager下载失败解
  5. android api 完整翻译之Contacts Provide
  6. android -h 'xcopy' 不是内部或外部命令
  7. Android应用程序汉化教程
  8. Android常用DOS命令
  9. 关于id的小知识
  10. Android的事件处理机制详解(一)-----基于