5.3 Thread类及常用同步类分析

Thread类是Android为线程操作而做的一个封装。代码在Thread.cpp中,其中还封装了一些与线程同步相关的类(既然是封装,要掌握它,最重要的当然是掌握与Pthread相关的知识)。我们先分析Threa类,进而再介绍与常用同步类相关的知识。

5.3.1 一个变量引发的思考

Thread类虽说挺简单,但其构造函数中的那个canCallJava却一度让我感到费解。因为我一直使用的是自己封装的Pthread类。当发现Thread构造函数中竟然存在这样一个东西时,很担心自己封装的Pthread类会不会有什么重大问题,因为当时我还从来没考虑过Java方面的问题。


//canCallJava表示这个线程是否会使用JNI函数。为什么需要一个这样的参数呢?

Thread(bool canCallJava=true)。


我们必须得了解它实际创建的线程函数是什么。Thread类真实的线程是创建在run函数中的。

1.一个变量,两种处理

先来看一段代码:


[—>Thread.cpp]

status_t Thread:run(const char*name,int32_t priority,size_t stack)

{

Mutex:Autolock_l(mLock);

……

//如果mCanCallJava为真,则调用createThreadEtc函数,线程函数是_threadLoop。

//_threadLoop是Thread.cpp中定义的一个函数。

if(mCanCallJava){

res=createThreadEtc(_threadLoop,this,name,priority,

stack,&mThread);

}else{

res=androidCreateRawThreadEtc(_threadLoop,this,name,priority,

stack,&mThread);

}


上面的mCanCallJava将线程创建函数的逻辑分为两个分支,虽传入的参数都有_threadLoop,但它们调用的函数却不同。先直接看mCanCallJava为true的这个分支,代码如下所示:


[—>Thread.h:createThreadEtc()函数]

inline bool createThreadEtc(thread_func_t entryFunction,

void*userData,

const char*threadName=“android:unnamed_thread”,

int32_t threadPriority=PRIORITY_DEFAULT,

size_t threadStackSize=0,

thread_id_t*threadId=0)

{

return androidCreateThreadEtc(entryFunction,userData,threadName,

threadPriority,threadStackSize,threadId)?true:false;

}

它调用的是androidCreateThreadEtc函数,相关代码如下所示:

//gCreateThreadFn是函数指针,它在初始化时和mCanCallJava为false时使用的是同一个

//线程创建函数。那么有地方会修改它吗?

static android_create_thread_fn gCreateThreadFn=androidCreateRawThreadEtc;

int androidCreateThreadEtc(android_thread_func_t entryFunction,

voiduserData,const charthreadName,

int32_t threadPriority,size_t threadStackSize,

android_thread_id_t*threadId)

{

return gCreateThreadFn(entryFunction,userData,threadName,

threadPriority,threadStackSize,threadId);

}


如果没有人修改这个函数指针,那么mCanCallJava就是虚晃一枪,并无什么作用。不过,代码中有的地方是会修改这个函数指针的指向的,请看——

2.zygote偷梁换柱

在本书4.2.1节的第2点所介绍的AndroidRuntime调用startReg的地方,就有可能修改这个函数指针,其代码如下所示:


[—>AndroidRuntime.cpp]

/static/int AndroidRuntime:startReg(JNIEnv*env)

{

//这里会修改函数指针为javaCreateThreadEtc。

androidSetCreateThreadFunc((android_create_thread_fn)javaCreateThreadEtc);

return 0;

}


如果mCanCallJava为true,则将调用javaCreateThreadEtc。那么,这个函数有什么特殊之处呢?来看其代码,如下所示:


[—>AndroidRuntime.cpp]

int AndroidRuntime:javaCreateThreadEtc(

android_thread_func_t entryFunction,

void*userData,

const char*threadName,

int32_t threadPriority,

size_t threadStackSize,

android_thread_id_t*threadId)

{

voidargs=(void)malloc(3sizeof(void));

int result;

args[0]=(void*)entryFunction;

args[1]=userData;

args[2]=(void*)strdup(threadName);

//调用的还是androidCreateRawThreadEtc,但线程函数却换成了javaThreadShell。

result=androidCreateRawThreadEtc(AndroidRuntime:javaThreadShell,args,

threadName,threadPriority,threadStackSize,threadId);

return result;

}

[—>AndroidRuntime.cpp]

int AndroidRuntime:javaThreadShell(void*args){

……

int result;

//把这个线程attach到JNI环境中,这样这个线程就可以调用JNI的函数了。

if(javaAttachThread(name,&env)!=JNI_OK)

return-1;

//调用实际的线程函数干活。

result=(*(android_thread_func_t)start)(userData);

//从JNI环境中detach出来。

javaDetachThread();

free(name);

return result;

}


3.费力能讨好

你明白mCanCallJava为true的目的了吗?它创建的新线程将:

在调用你的线程函数之前会attach到JNI环境中,这样,你的线程函数就可以无忧无虑地使用JNI函数了。

线程函数退出后,它会从JNI环境中detach,释放一些资源。

注意 第二点尤其重要,因为进程退出前,dalvik虚拟机会检查是否有attach了,如果最后有未detach的线程,则会直接abort(这不是一件好事)。如果你关闭JNI check选项,就不会做这个检查,但我觉得,这个检查和资源释放有关系,建议还是重视。如果直接使用POSIX的线程创建函数,那么凡是使用过attach的,最后就都需要detach!

Android为了dalvik的健康真是费尽心机呀。

4.线程函数_threadLoop介绍

无论一分为二是如何处理的,最终都会调用线程函数_threadLoop,为什么不直接调用用户传入的线程函数呢?莫非_threadLoop会有什么暗箱操作吗?下面我们来看:


[—>Thread.cpp]

int Thread:_threadLoop(void*user)

{

Threadconst self=static_cast<Thread>(user);

sp<Thread>strong(self->mHoldSelf);

wp<Thread>weak(strong);

self->mHoldSelf.clear();

if HAVE_ANDROID_OS

self->mTid=gettid();

endif

bool first=true;

do{

bool result;

if(first){

first=false;

//self代表继承Thread类的对象,第一次进来时将调用readyToRun,看看是否准备好。

self->mStatus=self->readyToRun();

result=(self->mStatus==NO_ERROR);

if(result&&!self->mExitPending){

result=self->threadLoop();

}

}else{

/*

调用子类实现的threadLoop函数,注意这段代码运行在一个do-while循环中。

这表示即使我们的threadLoop返回了,线程也不一定会退出。

*/

result=self->threadLoop();

}

/*

线程退出的条件:

1)result为false。这表明,如果子类在threadLoop中返回false,线程就可以退出。这属于主动退出的情况,是threadLoop自己不想继续干活了,所以返回false。读者在自己的代码中千万别写错threadLoop的返回值。

2)mExitPending为true,这个变量可由Thread类的requestExit函数设置,这种情况属于被动退出,因为由外界强制设置了退出条件。

*/

if(result==false||self->mExitPending){

self->mExitPending=true;

self->mLock.lock();

self->mRunning=false;

self->mThreadExitedCondition.broadcast();

self->mLock.unlock();

break;

}

strong.clear();

strong=weak.promote();

}while(strong!=0);

return 0;

}


关于_threadLoop,我们就介绍到这里。请读者务必注意下面一点:

threadLoop运行在一个循环中,它的返回值可以决定是否退出线程。