5.3.1 一个变量引发的思考
最后更新于:2022-04-02 05:49:56
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_tpriority, 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_tentryFunction,
void *userData,
const char*threadName = "android:unnamed_thread",
int32_tthreadPriority = PRIORITY_DEFAULT,
size_tthreadStackSize = 0,
thread_id_t*threadId = 0)
{
returnandroidCreateThreadEtc(entryFunction, userData, threadName,
threadPriority, threadStackSize,threadId) ? true : false;
}
~~~
它调用的是androidCreateThreadEtc函数,相关代码如下所示:
~~~
// gCreateThreadFn是函数指针,初始化时和mCanCallJava为false时使用的是同一个
//线程创建函数。那么有地方会修改它吗?
static android_create_thread_fn gCreateThreadFn= androidCreateRawThreadEtc;
int androidCreateThreadEtc(android_thread_func_tentryFunction,
void*userData,const char* threadName,
int32_tthreadPriority,size_t threadStackSize,
android_thread_id_t*threadId)
{
returngCreateThreadFn(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);
return0;
}
~~~
所以,如果mCanCallJava为true,则将调用javaCreateThreadEtc。那么,这个函数有什么特殊之处呢?来看其代码,如下所示:
**AndroidRuntime.cpp**
~~~
int AndroidRuntime::javaCreateThreadEtc(
android_thread_func_tentryFunction,
void* userData,
const char*threadName,
int32_tthreadPriority,
size_t threadStackSize,
android_thread_id_t* threadId)
{
void**args = (void**) malloc(3 * sizeof(void*));
intresult;
args[0] = (void*) entryFunction;
args[1] = userData;
args[2] = (void*) strdup(threadName);
//调用的还是androidCreateRawThreadEtc,但线程函数却换成了javaThreadShell。
result= androidCreateRawThreadEtc(AndroidRuntime::javaThreadShell, args,
threadName, threadPriority,threadStackSize, threadId);
returnresult;
}
~~~
**AndroidRuntime.cpp**
~~~
int AndroidRuntime::javaThreadShell(void* args){
......
intresult;
//把这个线程attach到JNI环境中,这样这个线程就可以调用JNI的函数了
if(javaAttachThread(name, &env) != JNI_OK)
return -1;
//调用实际的线程函数干活
result = (*(android_thread_func_t)start)(userData);
//从JNI环境中detach出来。
javaDetachThread();
free(name);
returnresult;
}
~~~
3. 费力而讨好
你明白mCanCallJava为true的目的了吗?它创建的新线程将:
- 在调用你的线程函数之前会attach到 JNI环境中,这样,你的线程函数就可以无忧无虑地使用JNI函数了。
- 线程函数退出后,它会从JNI环境中detach,释放一些资源。
第二点尤其重要,因为进程退出前,dalvik虚拟机会检查是否有attach了,但是最后未detach的线程如果有,则会直接abort(这不是一件好事)。如果你关闭JNI check选项,就不会做这个检查,但我觉得,这个检查和资源释放有关系。建议还是重视JNIcheck。如果直接使用POSIX的线程创建函数,那么凡是使用过attach的,最后就都需要detach!
Android为了dalvik的健康真是费尽心机呀。
4. 线程函数_threadLoop介绍
不论一分为二是如何处理的,最终的线程函数_threadLoop都会被调用,为什么不直接调用用户传入的线程函数呢?莫非_threadLoop会有什么暗箱操作吗?下面,我们来看:
**Thread.cpp**
~~~
int Thread::_threadLoop(void* user)
{
Thread* const self = static_cast(user);
sp strong(self->mHoldSelf);
wp weak(strong);
self->mHoldSelf.clear();
#if HAVE_ANDROID_OS
self->mTid = gettid();
#endif
boolfirst = 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);
return0;
}
~~~
关于_threadLoop,我们就介绍到这里。请读者务必注意下面一点:
- threadLoop运行在一个循环中,它的返回值可以决定是否退出线程。
';