Java多线程小结

  在Android中,因为性能等多方面因素,多线程使用的场景较多,基于多线程的消息机制Handler、异步处理AsyncTask、回调方法等也常常会遇到,这里简要分析下Java多线程的使用和原理(针对Thread和Runnable,Callable等不在讨论范围内)html

 

建立多线程

  java端多线程的使用比较简单,JDK提供了接口Runnable类Thread以供多线程使用,实现run方法,执行start函数启动便可,网上例子不少,这边给出最简单的使用:java

//1.继承Thread类
public class MyThread extends Thread{
    @Override
    public void run(){
        ...
    }
}

MyThread mThread = new MyThread();
mThread.start();



//2.实现Runnable接口
public class MyRunnable implements Runnable {
    @Override
    public void run(){
        ...
    }
}

Thread thread = new Thread(new MyRunnable());
thread.start();

  众所周知,重写run方法,就是肯定线程的执行方法;而调用start函数,就是启动多线程执行。若是只调用run方法,和调用正常函数并没有区别,仍是在当前线程执行。c++

  之因此在实现Runnable接口以后还须要定义Thread对象来调用start函数,就是由于Runnable接口没有start函数 (接口的方法是抽象方法,不能有具体实现,必须在子类覆盖实现,而本身实现start函数又不怎么现实),因此只能调用其子类Thread提供的start完成多线程的建立执行。数组

  这里给出Thread和Runnable的部分源码:安全

 

//Runnable接口很是简单,只有一个虚方法run
public interface Runnable {
    public abstract void run();
}

//Thread类实现了Runnable,并提供了start方法(下面具体分析)
public class Thread implements Runnable {    
    public synchronized void start() {
        ...
    }
    
    private Runnable target;
    
    @Override
    public void run() {
        //若是Thread的run未被重写,且Runnable对象不为空,则调用Runnable的run
        //然而Runnable是接口,不能实例化,run方法也不能实现
        //这种状况其实就是定义了类A实现了Runnable接口,并用类A的对象做为参数建立了Thread对象
        if (target != null) {
            target.run();
        }
    }
}

 

 

 

  除了经过定义子类的方式实现多线程,固然也能够经过使用匿名内部类,一样是依据类Thread或接口Runnable多线程

public static void main(String[] args) {
    //3.匿名内部类继承thread
    new Thread() {
        public void run() {
            ...
        }
    }.start();

    //4.匿名内部类实现runnable,一样依赖于Thread的start建立执行线程
    new Thread(new Runnable() {
        @Override
        public void run() {
            ...
        }
    }).start();
}

  从上面的使用实例能够看出,多线程的启动执行工做就在这个start函数中,下面来具体看看app

 

start函数简介

  首先看下start函数在Thread.java中的定义异步

public class Thread implements Runnable {
    public synchronized void start() {

        //防止一些由VM启动的线程被人为调用启动(主线程或系统组线程)
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        //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 void start0();
}

  通过一些异常处理和状态记录后,启动操做交给底层的C++去实现,启动函数就是这个start0ide

  按照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 class Thread implements Runnable {

    //系统注释:确保这个函数是该接口初始化时第一个调用的
    //这个函数是用于JNI关联C++方法和native函数的,Thread中的线程操做函数并无使用JNI中传统的名称对应规则,因此须要用这个函数保证native函数有定义可用
    private static native void registerNatives();

    //放在static块中,在初始化Thread类时执行一次注册便可,与对象无关
    static {
        registerNatives();
    }
}

//对应的C++方法,使用了传统的JNI调用函数命名规则,就在以前提到的Thread.c文件中
JNIEXPORT void JNICALL
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) {
        delete native_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 = new OSThread(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);
      delete osthread;
      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);
    delete osthread;
    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);
}

void JavaThread::run() {
  thread_main_inner();
}

void JavaThread::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的篇幅比较少,放在一块儿简述下

void JavaThread::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");
}
相关文章
相关标签/搜索