Java并發(fā)問題-join的實(shí)現(xiàn)原理

當(dāng)我們想要線程按照一定的執(zhí)行順序執(zhí)行峡钓,通常最簡單直接的方式就是使用到j(luò)oin方法。
join屬于對象方法若河,通過線程實(shí)例可以調(diào)用能岩。他的作用是阻塞等待線程執(zhí)行結(jié)束,再執(zhí)行后續(xù)的代碼萧福。接下來展開說明join的實(shí)現(xiàn)原理拉鹃。

線程的waiting狀態(tài)

JAVA中能時(shí)線程進(jìn)入waiting的方法有obj.wait(),thread.join(),LockSupport.park()。waiting狀態(tài)的線程需要被喚醒轉(zhuǎn)化為runnable狀態(tài),等待CPU調(diào)度膏燕。而喚醒線程的方法通過執(zhí)行notify/notifyAll方法钥屈,JVM調(diào)用操作系統(tǒng)命令實(shí)現(xiàn)。
我們來看下join方法

  public final void join() throws InterruptedException {
        join(0);
    }

    public final synchronized void join(long millis)
    throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;

        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }
// 參數(shù)為0 調(diào)用
        if (millis == 0) {
            while (isAlive()) {
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }
// 實(shí)際上調(diào)用的是Object的wait()方法
public final native void wait(long timeout) throws InterruptedException;

可以知道坝辫,join方法是通過不斷的循環(huán)調(diào)用wait()方法是實(shí)現(xiàn)等待篷就,那么什么時(shí)候會(huì)喚醒線程繼續(xù)執(zhí)行?join的作用是等待線程執(zhí)行完成阀溶,那么可以猜想腻脏,再線程結(jié)束執(zhí)行時(shí)是否會(huì)喚醒線程?
我們看下Thread的start方法,實(shí)際上調(diào)用的是native方法start0()套耕,這時(shí)需要看下JDK源碼下Thread.c找到start0方法定義

    {"start0",           "()V",        (void *)&JVM_StartThread},

再查看hotspot源碼栏笆,跟蹤到j(luò)vm.cpp

JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread))
  JVMWrapper("JVM_StartThread");
  JavaThread *native_thread = NULL;

  // We cannot hold the Threads_lock when we throw an exception,
  // due to rank ordering issues. Example:  we might need to grab the
  // Heap_lock while we construct the exception.
  bool throw_illegal_thread_state = false;

  // We must release the Threads_lock before we can post a jvmti event
  // in Thread::start.
  {
    // Ensure that the C++ Thread and OSThread structures aren't freed before
    // we operate.
    MutexLocker mu(Threads_lock);

    // Since JDK 5 the java.lang.Thread threadStatus is used to prevent
    // re-starting an already started thread, so we should usually find
    // that the JavaThread is null. However for a JNI attached thread
    // there is a small window between the Thread object being created
    // (with its JavaThread set) and the update to its threadStatus, so we
    // have to check for this
    if (java_lang_Thread::thread(JNIHandles::resolve_non_null(jthread)) != NULL) {
      throw_illegal_thread_state = true;
    } else {
      // We could also check the stillborn flag to see if this thread was already stopped, but
      // for historical reasons we let the thread detect that itself when it starts running

      jlong size =
             java_lang_Thread::stackSize(JNIHandles::resolve_non_null(jthread));
      // Allocate the C++ Thread structure and create the native thread.  The
      // stack size retrieved from java is signed, but the constructor takes
      // size_t (an unsigned type), so avoid passing negative values which would
      // result in really large stacks.
      size_t sz = size > 0 ? (size_t) size : 0;
      native_thread = new JavaThread(&thread_entry, sz);

      // At this point it may be possible that no osthread was created for the
      // JavaThread due to lack of memory. Check for this situation and throw
      // an exception if necessary. Eventually we may want to change this so
      // that we only grab the lock if the thread was created successfully -
      // then we can also do this check and throw the exception in the
      // JavaThread constructor.
      if (native_thread->osthread() != NULL) {
        // Note: the current thread is not being used within "prepare".
        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) {
    // No one should hold a reference to the 'native_thread'.
    delete native_thread;
    if (JvmtiExport::should_post_resource_exhausted()) {
      JvmtiExport::post_resource_exhausted(
        JVMTI_RESOURCE_EXHAUSTED_OOM_ERROR | JVMTI_RESOURCE_EXHAUSTED_THREADS,
        "unable to create new native thread");
    }
    THROW_MSG(vmSymbols::java_lang_OutOfMemoryError(),
              "unable to create new native thread");
  }
// 跟蹤這個(gè)方法 hotspot->thread.cpp
  Thread::start(native_thread);

JVM_END

thread.cpp:

void Thread::start(Thread* thread) {
  trace("start", thread);
  // Start is different from resume in that its safety is guaranteed by context or
  // being called from a Java method synchronized on the Thread object.
  if (!DisableStartThread) {
    if (thread->is_Java_thread()) {
      // Initialize the thread state to RUNNABLE before starting this thread.
      // Can not set it after the thread started because we do not know the
      // exact thread state at that time. It could be in MONITOR_WAIT or
      // in SLEEPING or some other state.
      java_lang_Thread::set_thread_status(((JavaThread*)thread)->threadObj(),
                                          java_lang_Thread::RUNNABLE);
    }
// 跟蹤hotspot os.cpp
    os::start_thread(thread);
  }
}

os.cpp:

void os::start_thread(Thread* thread) {
  // guard suspend/resume
  MutexLockerEx ml(thread->SR_lock(), Mutex::_no_safepoint_check_flag);
  OSThread* osthread = thread->osthread();
  osthread->set_state(RUNNABLE);
  // 啟動(dòng)的方法  
  pd_start_thread(thread);
}

執(zhí)行啟動(dòng)線程命令,則我們可以猜想下虐呻,再線程結(jié)束的時(shí)候是否會(huì)喚醒線程。跟蹤到線程結(jié)束的地方(由于hotspot源碼調(diào)用關(guān)系較多,省略跟蹤過程)更振。最終調(diào)用的方法是hotspot\src\os\windows\vm\os_windows.cpp#os::pd_start_thread,linux對應(yīng)os_linux.cpp

// windows
void os::pd_start_thread(Thread* thread) {
// 調(diào)用操作系統(tǒng)回復(fù)線程命令喚醒
  DWORD ret = ResumeThread(thread->osthread()->thread_handle());
  // Returns previous suspend state:
  // 0:  Thread was not suspended
  // 1:  Thread is running now
  // >1: Thread is still suspended.
  assert(ret != SYS_THREAD_ERROR, "StartThread failed"); // should propagate back
}

//linux
void os::pd_start_thread(Thread* thread) {
  OSThread * osthread = thread->osthread();
  assert(osthread->get_state() != INITIALIZED, "just checking");
  Monitor* sync_with_child = osthread->startThread_lock();
  MutexLockerEx ml(sync_with_child, Mutex::_no_safepoint_check_flag);
// notify 調(diào)用操作系統(tǒng)命令喚醒線程
  sync_with_child->notify();
}

可知join命令實(shí)質(zhì)上是通過,wait/notify操作實(shí)現(xiàn)的線程等待饭尝。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末肯腕,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子钥平,更是在濱河造成了極大的恐慌实撒,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,427評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件涉瘾,死亡現(xiàn)場離奇詭異知态,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)立叛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評論 3 395
  • 文/潘曉璐 我一進(jìn)店門负敏,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人秘蛇,你說我怎么就攤上這事其做。” “怎么了赁还?”我有些...
    開封第一講書人閱讀 165,747評論 0 356
  • 文/不壞的土叔 我叫張陵妖泄,是天一觀的道長。 經(jīng)常有香客問我秽浇,道長浮庐,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,939評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮审残,結(jié)果婚禮上梭域,老公的妹妹穿的比我還像新娘。我一直安慰自己搅轿,他們只是感情好病涨,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,955評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著璧坟,像睡著了一般既穆。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上雀鹃,一...
    開封第一講書人閱讀 51,737評論 1 305
  • 那天幻工,我揣著相機(jī)與錄音,去河邊找鬼黎茎。 笑死囊颅,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的傅瞻。 我是一名探鬼主播踢代,決...
    沈念sama閱讀 40,448評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼嗅骄!你這毒婦竟也來了胳挎?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,352評論 0 276
  • 序言:老撾萬榮一對情侶失蹤溺森,失蹤者是張志新(化名)和其女友劉穎慕爬,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體儿惫,經(jīng)...
    沈念sama閱讀 45,834評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡澡罚,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,992評論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了肾请。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片留搔。...
    茶點(diǎn)故事閱讀 40,133評論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖铛铁,靈堂內(nèi)的尸體忽然破棺而出隔显,到底是詐尸還是另有隱情,我是刑警寧澤饵逐,帶...
    沈念sama閱讀 35,815評論 5 346
  • 正文 年R本政府宣布括眠,位于F島的核電站,受9級特大地震影響倍权,放射性物質(zhì)發(fā)生泄漏掷豺。R本人自食惡果不足惜捞烟,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,477評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望当船。 院中可真熱鬧题画,春花似錦、人聲如沸德频。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽壹置。三九已至竞思,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間钞护,已是汗流浹背盖喷。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留患亿,地道東北人传蹈。 一個(gè)月前我還...
    沈念sama閱讀 48,398評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像步藕,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子挑格,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,077評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 一咙冗、進(jìn)程和線程 進(jìn)程 進(jìn)程就是一個(gè)執(zhí)行中的程序?qū)嵗總€(gè)進(jìn)程都有自己獨(dú)立的一塊內(nèi)存空間漂彤,一個(gè)進(jìn)程中可以有多個(gè)線程雾消。...
    阿敏其人閱讀 2,612評論 0 13
  • layout: posttitle: 《Java并發(fā)編程的藝術(shù)》筆記categories: Javaexcerpt...
    xiaogmail閱讀 5,826評論 1 19
  • Java自誕生開始就明智地選擇了內(nèi)置對多線程的支持,這使得Java語言相比同一時(shí)期的其他語言具有明顯的優(yōu)勢挫望。線程作...
    shallowinggg閱讀 495評論 0 2
  • Java多線程學(xué)習(xí) [-] 一擴(kuò)展javalangThread類 二實(shí)現(xiàn)javalangRunnable接口 三T...
    影馳閱讀 2,959評論 1 18
  • 本文主要講了java中多線程的使用方法立润、線程同步、線程數(shù)據(jù)傳遞媳板、線程狀態(tài)及相應(yīng)的一些線程函數(shù)用法桑腮、概述等。 首先講...
    李欣陽閱讀 2,456評論 1 15