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运行在一个循环中,它的返回值可以决定是否退出线程。