Java多线程小结

Posted 九鼎煮鸡蛋

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java多线程小结相关的知识,希望对你有一定的参考价值。

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

 

创建多线程

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

//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方法,和调用正常函数并无区别,还是在当前线程执行。

  之所以在实现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函数中,下面来具体看看

 

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++去实现,启动函数就是这个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 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");
}

以上是关于Java多线程小结的主要内容,如果未能解决你的问题,请参考以下文章

Java多线程小结

java多线程面试题小结

《java并发编程的艺术》学习小结

《java并发编程的艺术》学习小结

java concurrent包常用类小结

多线程小结