在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");

}

java thread detach_Java多线程小结相关推荐

  1. Java并发编程(6):Runnable和Thread实现多线程的区别(含代码)

    Java中实现多线程有两种方法:继承Thread类.实现Runnable接口,在程序开发中只要是多线程,肯定永远以实现Runnable接口为主,因为实现Runnable接口相比继承Thread类有如下 ...

  2. Thread 实现多线程同步下载网络图片(Java)

    Thread 实现多线程同步下载网络图片(Java) 1.自定义线程类继承Thread类 2.重写run()方法,编写线程执行体 3.创建线程对象,调用start()方法启动线程 4.Thread 实 ...

  3. 【Java并发编程】:Runnable和Thread实现多线程的区别

    Java中实现多线程有两种方法:继承Thread类.实现Runnable接口,在程序开发中只要是多线程,肯定永远以实现Runnable接口为主,因为实现Runnable接口相比继承Thread类有如下 ...

  4. JAVA并发与多线程相关面试题总结

    JAVA并发与多线程相关面试题总结 1.什么是进程.线程.协程,它们之间的关系是怎样的? 进程: 本质上是一个独立执行的程序,是计算机中的程序关于数据集合上的一次运行活动,进程是操作系统进行资源分配和 ...

  5. JAVA并发之多线程基础(2)

    除了我们经常用的synchronized关键字(结合Object的wait()和notify()使用)之外,还有对应的上篇文章讲到的方法JAVA并发之多线程基础(1)之外,我们日常中使用到最多的也就是 ...

  6. Java基础、多线程、JVM、集合八股文自述(持续更新)

    Java基础.多线程.JVM.集合八股文自述 一.Java基础 1.1 object类有哪些方法? getClass().hashCode().equals().clone().toString(). ...

  7. Java中的多线程编程(超详细总结)

    文章目录 Java中的多线程编程(超详细总结) 一.线程与多线程的概念 二.线程与进程之间的关系 三.一个线程的生命周期 四.多线程的目的和意义 五.线程的实现的方式 Java中的多线程编程(超详细总 ...

  8. Java回顾之多线程

    在这篇文章里,我们关注多线程.多线程是一个复杂的话题,包含了很多内容,这篇文章主要关注线程的基本属性.如何创建线程.线程的状态切换以及线程通信,我们把线程同步的话题留到下一篇文章中. 线程是操作系统运 ...

  9. Java基础之多线程详细分析

    在了解多线程之前,先来了解一下进程与线程之间的关系. 进程和线程: 进程是指在系统中正在执行的一个程序,每个进程之间是独立的. 线程是进程的一个基本执行单元.一个进程要想执行任务,必须得有线程(每1个 ...

  10. java.text.SimpleDateFormat多线程下的问题

    1. 今天在做性能压测的时候发现java.text.SimpleDateFormat多线程下的错误 2. 先贴出两段错误大家看一下: Exception in thread "pool-1- ...

最新文章

  1. 易生信群体和单细胞转录组专题第6期于5月10日在北京开课了
  2. Android 自定义长按响应时间
  3. python ajax mysql_Python开发【第十六篇】:AJAX全套
  4. 数据挖掘的一个完整过程
  5. EL表达式JSON应用
  6. android os苹果手机助手,深度系统V20(1003)内测招募:新增手机助手,支持安卓/iOS端...
  7. vue2.0的Element UI的表格table列时间戳格式化
  8. LeetCode:每日一题(2020.4.10)
  9. Java日志设计实践(1) - 常见问题
  10. harmonyos2.0系统,Harmonyos2.0刷机包
  11. 单片机小车关于PWM控速
  12. 台式计算机怎么安装无线网卡,台式机无线网卡怎么用 台式机USB无线网卡安装使用教程...
  13. 恢复未保存的word
  14. 项目研发阶段性总结模板
  15. maskrcnn训练问题报错:selected_polygons.append(self.polygons[i]) IndexError: list index out of range
  16. 华为云桌面客户端_华为云服务器购买及环境搭建简述
  17. Window通过cmd命令测试服务器带宽性能的方法(IP测试)
  18. 转给你身边的工程师!从零开始搭建一个完整AGV控制系统
  19. springboot考研规划系统 毕业设计-附源码541230
  20. 知道创宇区块链安全实验室 | OneRing Finance 闪电贷攻击事件分析

热门文章

  1. 【优化算法】鸽群优化算法(PIO)【含Matlab源码 1077期】
  2. 【数字信号处理】基于matlab GUI IIR低通+FIR高通信号时域+频谱分析【含Matlab源码 1029期】
  3. 【水果识别】基于matlab GUI苹果质量检测及分级系统【含Matlab源码 896期】
  4. 【数字信号调制】基于matlab GUI BPSK调制+解调【含Matlab源码 644期】
  5. 【图像处理】基于matlab图像RGB三色合成+分离【含Matlab源码 401期】
  6. 【优化预测】基于matlab飞蛾扑火算法优化LSSVM预测【含Matlab源码 110期】
  7. 深度学习行人检测简介_深度学习简介
  8. js ajax异步提交,jquery ajax异步提交表单数据的方法
  9. linux6.5禁用防火墙,Centos6.5,Centos7分别关闭selinux和防火墙
  10. 所有手机品牌型号大全_【干货】史上最全SMT贴片机品牌、型号大全,赶紧看看你会几种???...