在Android中,由于性能等多方面因素,多线程使用的场景较多,基于多线程的消息机制Handler、异步处理AsyncTask、回调方法等也经常会遇到,这里简要分析下Java多线程的使用和原理(针对Thread和Runnable,Callable等不在讨论范围内)
创建多线程
java端多线程的使用比较简单,JDK提供了接口Runnable和类Thread以供多线程使用,实现run方法,执行start函数启动即可,网上例子很多,这边给出最简单的使用:
//1.继承Thread类
public class MyThread extends Thread{
@Overridepublic void run(){
...
}
}
MyThread mThread= newMyThread();
mThread.start();//2.实现Runnable接口
public class MyRunnable implements Runnable {
@Override
public void run(){
...
}
}
Thread thread = new Thread(new MyRunnable());
thread.start();
众所周知,重写run方法,就是确定线程的执行方法;而调用start函数,就是启动多线程执行。如果只调用run方法,和调用正常函数并无区别,还是在当前线程执行。
之所以在实现Runnable接口之后还需要定义Thread对象来调用start函数,就是因为Runnable接口没有start函数 (接口的方法是抽象方法,不能有具体实现,必须在子类覆盖实现,而自己实现start函数又不怎么现实),所以只能调用其子类Thread提供的start完成多线程的创建执行。
这里给出Thread和Runnable的部分源码:
//Runnable接口非常简单,只有一个虚方法run
public interfaceRunnable {public abstract voidrun();
}//Thread类实现了Runnable,并提供了start方法(下面具体分析)
public class Thread implementsRunnable {public synchronized voidstart() {
...
}privateRunnable target;
@Overridepublic voidrun() {//如果Thread的run未被重写,且Runnable对象不为空,则调用Runnable的run//然而Runnable是接口,不能实例化,run方法也不能实现//这种情况其实就是定义了类A实现了Runnable接口,并用类A的对象作为参数创建了Thread对象
if (target != null) {
target.run();
}
}
}
除了通过定义子类的方式实现多线程,当然也可以通过使用匿名内部类,同样是依据类Thread或接口Runnable
public static voidmain(String[] args) {//3.匿名内部类继承thread
newThread() {public voidrun() {
...
}
}.start();//4.匿名内部类实现runnable,同样依赖于Thread的start创建执行线程
new Thread(newRunnable() {
@Overridepublic voidrun() {
...
}
}).start();
}
从上面的使用实例可以看出,多线程的启动执行工作就在这个start函数中,下面来具体看看
start函数简介
首先看下start函数在Thread.java中的定义
public class Thread implementsRunnable {public synchronized voidstart() {//防止一些由VM启动的线程被人为调用启动(主线程或系统组线程)
if (threadStatus != 0)throw newIllegalThreadStateException();//group主要是对线程队列的操作(记录线程状态,启动、阻塞等的计数等),对线程自身的运行无关
group.add(this);boolean started = false;try{//线程启动函数,native函数,具体实现在C++端
start0();
started= true;
}finally{try{if (!started) {
group.threadStartFailed(this);
}
}catch(Throwable ignore) {
}
}
}private native voidstart0();
}
经过一些异常处理和状态记录后,启动操作交给底层的C++去实现,启动函数就是这个start0。
按照openjdk的设计,java与c++相对应的类一般选择相同名称。查找后发现在/jdk/src/java.base/share/native/libjava文件夹下有一个同名文件Thread.c。
扫视一眼,Thread.c中确实有start0,但却没有遵循传统的JNI调用函数命名规则(Java_包层级目录_函数名),而是使用了对函数进行注册的机制——RegisterNatives
RegisterNatives的作用就是向VM注册 java方法C++函数 的映射关系,以便在java端调用native函数时可以快速地定位其对应的C++方法,而且便于修改:改变映射关系数组,再次调用RegisterNatives可覆盖之前的映射关系,而传统的JNI命名规则则需要修改C++方法
下面来看下Thread类是如何使用这个注册机制的:
在java端调用native函数之前,需要主动调用RegisterNatives进行native函数的注册,在Thread.java中,申明并调用了如下native函数
public classThread implements Runnable {//系统注释:确保这个函数是该接口初始化时第一个调用的//这个函数是用于JNI关联C++方法和native函数的,Thread中的线程操作函数并没有使用JNI中传统的名称对应规则,所以需要用这个函数保证native函数有定义可用
private static native voidregisterNatives();//放在static块中,在初始化Thread类时执行一次注册即可,与对象无关
static{
registerNatives();
}
}//对应的C++方法,使用了传统的JNI调用函数命名规则,就在之前提到的Thread.c文件中
JNIEXPORT voidJNICALL
Java_java_lang_Thread_registerNatives(JNIEnv*env, jclass cls)
{//向VM注册native函数的对应关系,此函数具体源码没有找到,这个methods就是native函数的映射表,下面会讲到,cls为类型,对应Java中的Thread,这样就能精确定位Java中的函数
(*env)->RegisterNatives(env, cls, methods, ARRAY_LENGTH(methods));
}
native函数与C++方法的对应关系结构体及Thread所用到的函数映射表
//描述一个native函数对象和其对应的C++方法
typedef struct{char *name; //native函数名
char *signature; //native函数签名(参数与返回值类型)
void *fnPtr; //对应的函数指针引用(C++中的具体实现函数)
} JNINativeMethod;//函数映射表methods
static JNINativeMethod methods[] ={
{"start0", "()V", (void *)&JVM_StartThread},
{"stop0", "(" OBJ ")V", (void *)&JVM_StopThread},
{"isAlive", "()Z", (void *)&JVM_IsThreadAlive},
{"suspend0", "()V", (void *)&JVM_SuspendThread},
{"resume0", "()V", (void *)&JVM_ResumeThread},
{"setPriority0", "(I)V", (void *)&JVM_SetThreadPriority},
{"yield", "()V", (void *)&JVM_Yield},
{"sleep", "(J)V", (void *)&JVM_Sleep},
{"currentThread", "()" THD, (void *)&JVM_CurrentThread},
{"countStackFrames", "()I", (void *)&JVM_CountStackFrames},
{"interrupt0", "()V", (void *)&JVM_Interrupt},
{"isInterrupted", "(Z)Z", (void *)&JVM_IsInterrupted},
{"holdsLock", "(" OBJ ")Z", (void *)&JVM_HoldsLock},
{"getThreads", "()[" THD, (void *)&JVM_GetAllThreads},
{"dumpThreads", "([" THD ")[[" STE, (void *)&JVM_DumpThreads},
{"setNativeName", "(" STR ")V", (void *)&JVM_SetNativeThreadName},
};
经过RegisterNatives的注册,Java端的native函数start0与C++端的JVM_StartThread形成对应关系,线程启动工作也就落在了JVM_StartThread函数中
//宏JVM_ENTRY--JVM_END,用来对函数进行定义,这里就是定义函数JVM_StartThread
JVM_ENTRY(void, JVM_StartThread(JNIEnv*env, jobject jthread))
JVMWrapper("JVM_StartThread");
JavaThread*native_thread =NULL;bool throw_illegal_thread_state = false;
{//在java线程创建成功(加入到线程队列)之前防止C++本地线程和相关平台数据被释放(平台数据用于创建线程时选择对应平台方法,c++本地线程就是之后的操作线程)
MutexLocker mu(Threads_lock);//防止对一个已存在的线程进行再次创建
if (java_lang_Thread::thread(JNIHandles::resolve_non_null(jthread)) !=NULL) {
throw_illegal_thread_state= true;
}else{
jlong size=java_lang_Thread::stackSize(JNIHandles::resolve_non_null(jthread));
size_t sz= size > 0 ? (size_t) size : 0;//创建本地线程(C++线程,根据不同平台选择不同的处理),设置线程处理函数为thread_entry(下面会讲到),这里的JavaThread就是主要的线程处理类//其属性包括java层线程对象、jni指针、java层引用计数等以及一系列编译优化内存控制项,控制本地线程的生命周期内活动、与Java端关联等一系列操作(属性、功能很多)
native_thread = new JavaThread(&thread_entry, sz);//经过上面的创建JavaThread对象之后,对象native_thread中应该包含有平台相关信息
if (native_thread->osthread() !=NULL) {//将本地线程加入线程链表、设置优先级,并与java线程对象相关联//作为参数的jthread其实就是java层调用start0的Thread类对象//这里通过句柄将jthread和C++线程关联,通过JNI可直接操作C++线程
native_thread->prepare(jthread);
}
}
}if(throw_illegal_thread_state) {
THROW(vmSymbols::java_lang_IllegalThreadStateException());
}
assert(native_thread!= NULL, "Starting null thread?");if (native_thread->osthread() ==NULL) {deletenative_thread;if(JvmtiExport::should_post_resource_exhausted()) {
JvmtiExport::post_resource_exhausted(
JVMTI_RESOURCE_EXHAUSTED_OOM_ERROR|JVMTI_RESOURCE_EXHAUSTED_THREADS,
os::native_thread_creation_failed_msg());
}
THROW_MSG(vmSymbols::java_lang_OutOfMemoryError(),
os::native_thread_creation_failed_msg());
}//本地线程在创建JavaThread对象时已经创建并初始化,在prepare时已与Java对象关联//线程在初始化状态默认被阻塞,在这里主要功能就是唤醒本地线程使其开始运行
Thread::start(native_thread);
JVM_END
从上面的代码中可以看到,除了一些异常处理外,使用到的重要函数有三个,分别是JavaThread、prepare和start。他们的基本功能已经作了相关注释,下面来具体看下
创建线程之JavaThread
JavaThread这个类相当的大,功能相当的多,呵呵!在这里只简述下主要流程
JavaThread::JavaThread(ThreadFunction entry_point, size_t stack_sz) : Thread()
{//初始化
initialize();//JNI附加状态,刚开始为未附加(此时线程尚未创建,当然未关联)
_jni_attach_state =_not_attaching_via_jni;//设置属性(线程函数)_entry_point为&thread_entry (参数entry_point对应的值为&thread_entry),后面会用到
set_entry_point(entry_point);//设置线程类型(便宜线程或处理线程)
os::ThreadType thr_type =os::java_thread;
thr_type= entry_point == &compiler_thread_entry ?os::compiler_thread : os::java_thread;//属性值设置完了,那毫无疑问最后这个函数就是最重要的线程创建函数了//注意到函数前的作用域os没,他就是操作系统接口类,提供基于平台的代码,也就是说,create_thread会根据平台不同而不同,这里主要介绍下Linux平台下的相关代码
os::create_thread(this, thr_type, stack_sz);
}
//Linux平台下对应的create_thread实现
bool os::create_thread(Thread*thread, ThreadType thr_type,
size_t req_stack_size) {
assert(thread->osthread() == NULL, "caller responsible");
OSThread* osthread = newOSThread(NULL, NULL);if (osthread ==NULL) {return false;
}//设置线程类型
osthread->set_thread_type(thr_type);//设置状态(内存分配、初始化等状态)
osthread->set_state(ALLOCATED);//设置平台相关数据
thread->set_osthread(osthread);
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);//堆栈大小设置什么的就略过了
size_t stack_size =os::Posix::get_initial_stack_size(thr_type, req_stack_size);
stack_size= align_size_up(stack_size +os::Linux::default_guard_size(thr_type), vm_page_size());
pthread_attr_setstacksize(&attr, stack_size);
pthread_attr_setguardsize(&attr, os::Linux::default_guard_size(thr_type));
ThreadState state;
{
pthread_t tid;//看到这句是不是很熟悉了,创建线程,处理函数为thread_native_entry//相应的Windows下为_beginthreadex;Solaris下为thr_create
int ret = pthread_create(&tid, &attr, (void* (*)(void*)) thread_native_entry, thread);char buf[64];if (ret == 0) {
log_info(os, thread)("Thread started (pthread id:" UINTX_FORMAT ", attributes: %s).",
(uintx) tid, os::Posix::describe_pthread_attr(buf,sizeof(buf), &attr));
}else{
log_warning(os, thread)("Failed to start thread - pthread_create failed (%s) for attributes: %s.",
os::errno_name(ret), os::Posix::describe_pthread_attr(buf,sizeof(buf), &attr));
}//删除临时数据、报错退出
pthread_attr_destroy(&attr);if (ret != 0) {//Need to clean up stuff we've allocated so far
thread->set_osthread(NULL);deleteosthread;return false;
}//向平台数据记录线程号
osthread->set_pthread_id(tid);//初始化成功或者中止
{
Monitor* sync_with_child = osthread->startThread_lock();
MutexLockerEx ml(sync_with_child, Mutex::_no_safepoint_check_flag);while ((state = osthread->get_state()) ==ALLOCATED) {
sync_with_child->wait(Mutex::_no_safepoint_check_flag);
}
}
}//达到上限,退出
if (state ==ZOMBIE) {
thread->set_osthread(NULL);deleteosthread;return false;
}//线程初始化成功,返回
//这里有句系统注释: The thread is returned suspended (in state INITIALIZED),也就是说,创建并初始化成功后,线程默认被阻塞,需要唤醒才能运行 assert(state == INITIALIZED, "race condition");return true;
}
经过函数create_thread处理之后,本地线程就已经创建成功并初始化,处理函数为thread_native_entry。如果正确返回,那此时线程就处于已初始化状态(此时线程尚未加入到进程的线程链表中,且被阻塞),如果返回错误,那说明创建失败。关于线程的状态,可以参考这篇文章。接下来的就是要看看thread_native_entry在哪定义,是否和我们想的一样,最终执行的是Java端所定义的函数run?
源码中安全检测类代码太多,这边就只列出主要函数
static unsigned __stdcall thread_native_entry(Thread*thread) {
__try {
thread->run();
} __except(topLevelExceptionFilter((_EXCEPTION_POINTERS*)_exception_info())) {
}return(unsigned)os::win32::exit_process_or_thread(os::win32::EPT_THREAD, res);
}voidJavaThread::run() {
thread_main_inner();
}voidJavaThread::thread_main_inner() {if (!this->has_pending_exception() &&
!java_lang_Thread::is_stillborn(this->threadObj())) {
{
ResourceMark rm(this);this->set_native_thread_name(this->get_thread_name());
}
HandleMark hm(this);this->entry_point()(this, this);
}
}
ThreadFunction entry_point()const { return _entry_point; }
进过上面的流程过滤,最后定位到了函数 _entry_point,那这个是什么呢?往回看一点点,对,就是在创建JavaThread的函数中,有一个set_entry_point(entry_point),功能是将_entry_point的值设置为&thread_entry,也就是这边所用的这个_entry_point,而其值就是&thread_entry,这个又是什么呢?来看看定义
static void thread_entry(JavaThread*thread, TRAPS) {
HandleMark hm(THREAD);
Handle obj(THREAD, thread->threadObj());
JavaValue result(T_VOID);//JavaCalls: openjdk中用于C++端调用Java端方法的功能类,在这里不再深入//显然这边就是调用最终的具体执行函数了,来看看是不是run方法//类vmSymbols是用于VM对所用的标识进行快速定位的,在他的命名空间中,定义有一系列(函数名,符号名)的对应关系//类SystemDictionary用作类加载器的辅助类,记录本地函数与Java类名的对应关系
JavaCalls::call_virtual(&result,
obj,
KlassHandle(THREAD, SystemDictionary::Thread_klass()),//类型java_lang_Thread的一个句柄
vmSymbols::run_method_name(), //函数名
vmSymbols::void_method_signature(), //函数签名
THREAD);
}//由此可以看出,上面的call_virtual调用的Java端函数为 void run();而函数的定位用到obj和KlassHandle//这个obj就是传入的Java层线程对象,KlassHandle对应于类型java_lang_Thread的一个句柄//call_virtual从名字就可以看出是虚函数调用约定,后面会涉及链接时函数定位、运行时函数定位等等,有兴趣的可以查看源码
template(run_method_name, "run")
template(void_method_signature,"()V")//Pre表示该类为预加载类,本地函数Thread_klass对应Java中的类java_lang_Thread
do_klass(Thread_klass,java_lang_Thread,Pre)
到这里,之前的new JavaThread(&thread_entry, sz)函数的功能大致已经清楚了,简单的概括就是:创建了一个本地线程,并使其处于已初始化状态,线程处理函数为Java层线程对象的run方法
prepare与start
prepare与start的篇幅比较少,放在一起简述下
voidJavaThread::prepare(jobject jni_thread, ThreadPriority prio) {//保证当前线程占有锁定资源
assert(Threads_lock->owner() == Thread::current(), "must have threads lock");//下面几个操作将Java层线程对象和C++层本地线程相互关联起来//系统注释中将jni_thread说成是C++线程对象,不甚理解,如果有大大懂得可以说下~~下面按自己的理解来叙述//将jni_thread(Java层线程对象)作为本地线程的一个句柄,thread_oop则指向jni_thread,之后通过thread_oop就可调用Java层Thread类对象
Handle thread_oop(Thread::current(),
JNIHandles::resolve_non_null(jni_thread));
assert(InstanceKlass::cast(thread_oop->klass())->is_linked(),"must be initialized");//设置_threadObj(JavaThread中用于表示Java层线程对象的属性),值为上面定义的句柄,这样就实现了C++调用Java的条件
set_threadObj(thread_oop());//向java.lang.Thread对象的接口类注册本地线程,这样就实现了Java调用C++的条件
java_lang_Thread::set_thread(thread_oop(), this);if (prio ==NoPriority) {
prio=java_lang_Thread::priority(thread_oop());
assert(prio!= NoPriority, "A valid priority should be present");
}//设置优先级
Thread::set_priority(this, prio);
prepare_ext();//将本地线程加入到线程队列
Threads::add(this);
}
用一句话来概括prepare的工作:关联Java层线程对象与C++本地线程,并将本地线程加入线程队列
到现在为止,就差一步线程就能开始工作了(还记得上面说的线程创建后默认被阻塞么),最后的工作就是将它唤醒,当然就是start的功能了
void Thread::start(Thread*thread) {//启动线程与恢复线程情况不同,这边要排除这种情况
if (!DisableStartThread) {if (thread->is_Java_thread()) {//设置线程状态为运行
java_lang_Thread::set_thread_status(((JavaThread*)thread)->threadObj(),
java_lang_Thread::RUNNABLE);
}
os::start_thread(thread);
}
}void os::start_thread(Thread*thread) {
MutexLockerEx ml(thread->SR_lock(), Mutex::_no_safepoint_check_flag);
OSThread* osthread = thread->osthread();//设置平台相关数据的状态为运行
osthread->set_state(RUNNABLE);//调用各平台的唤醒函数启动线程
pd_start_thread(thread);
}//Windows的比较直观,分析Windows的
void os::pd_start_thread(Thread*thread) {//唤醒本地线程使之运行,对应Linxu的notify,Solaris的thr_continue
DWORD ret = ResumeThread(thread->osthread()->thread_handle());
assert(ret!= SYS_THREAD_ERROR, "StartThread failed");
}