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");
}
相關文章
相關標籤/搜索