Thread源碼剖析

對(duì)于線程Thread類(lèi)的使用花椭,可以說(shuō)是java語(yǔ)言必備阿浓,但你是否真正意義上去剖析過(guò)他的內(nèi)部結(jié)構(gòu)堕战,本文從概述的幾個(gè)問(wèn)題出發(fā)补君,一起進(jìn)行源碼閱讀(本文基于Android-27中的Thread源碼

概述

對(duì)常用的Thread做一次源碼剖析引几,更好的去理解和使用它,看完之后你會(huì)明白的幾個(gè)問(wèn)題:

  1. 調(diào)用start發(fā)生了什么挽铁?多次調(diào)用start會(huì)怎么樣伟桅?
  2. start和run方法的區(qū)別
  3. join和sleep的區(qū)別
  4. 什么是守護(hù)進(jìn)程

一、創(chuàng)建使用

1. 初始化

Thread構(gòu)造函數(shù)

內(nèi)部調(diào)用--->init()方法

java.lang.Thread#Thread()
java.lang.Thread#Thread(java.lang.Runnable)
java.lang.Thread#Thread(java.lang.ThreadGroup, java.lang.Runnable)
java.lang.Thread#Thread(java.lang.String)
java.lang.Thread#Thread(java.lang.ThreadGroup, java.lang.String)
java.lang.Thread#Thread(java.lang.ThreadGroup, java.lang.String, int, boolean)
java.lang.Thread#Thread(java.lang.Runnable, java.lang.String)
java.lang.Thread#Thread(java.lang.ThreadGroup, java.lang.Runnable, java.lang.String)
java.lang.Thread#Thread(java.lang.ThreadGroup, java.lang.Runnable, java.lang.String, long)

init()方法指定四個(gè)參數(shù):ThreadGroup叽掘,任務(wù)runable楣铁,線程名稱(chēng),棧大小更扁,其中部分參數(shù)初始值都是繼承父線程的屬性

private void init(ThreadGroup g, Runnable target, String name, long stackSize) {
    Thread parent = currentThread();//獲取創(chuàng)建thread的線程
    if (g == null) {
        g = parent.getThreadGroup();
    }

    g.addUnstarted();//在ThreadGroup中標(biāo)記增加了一個(gè)未啟動(dòng)的線程盖腕,里面操作很簡(jiǎn)單,nUnstartedThreads++;
    this.group = g;

    this.target = target;
    this.priority = parent.getPriority();//繼承父線程的等級(jí)
    this.daemon = parent.isDaemon();//繼承父線程的屬性:是否為守護(hù)進(jìn)程
    setName(name);

    init2(parent);//保存一些常量參數(shù)浓镜,如上溃列,給子線程調(diào)用

    /* Stash the specified stack size in case the VM cares */
    this.stackSize = stackSize;
    tid = nextThreadID();
}

...

//線程 tid遞增一個(gè)
private static synchronized long nextThreadID() {
    return ++threadSeqNumber;
}

2. start方法

  • 在Android中,檢測(cè)到再次調(diào)用start線程會(huì)拋出IllegalThreadStateException

      public synchronized void start() {
          
          // Android-changed: throw if 'started' is true
          if (threadStatus != 0 || started)
              throw new IllegalThreadStateException();
    
          //還記得上面init方法中膛薛,調(diào)用addUnstarted時(shí)哭廉,標(biāo)記增加了未啟動(dòng)線程
          //這里調(diào)用add方法,將線程添加到系統(tǒng)線程數(shù)組相叁,并且將未啟動(dòng)線程數(shù)減一遵绰,相當(dāng)于移出
          group.add(this);
    
          started = false;
          try {
              nativeCreate(this, stackSize, daemon);
              //調(diào)用native方法啟動(dòng)線程,如果報(bào)錯(cuò)增淹,則直接跳到finally執(zhí)行椿访,started為false,
              //啟動(dòng)失敗,從group中移除虑润,同時(shí)group中未啟動(dòng)線程數(shù)++
              started = true;
          } finally {
              try {
                  if (!started) {
                      group.threadStartFailed(this);
                  }
              } catch (Throwable ignore) {
                  /* do nothing. If start0 threw a Throwable then
                    it will be passed up the call stack */
              }
          }
      } 
    

3. run方法

//Thread實(shí)現(xiàn)的Runnable接口
class Thread implements Runnable {

    ...
    
    //調(diào)用傳入的Runnable的run方法
    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }

二成玫、Thread阻塞

1.join方法

join方法用于等待線程執(zhí)行完成,傳入的時(shí)間單位為等待的最大時(shí)長(zhǎng),里面是一個(gè) while (isAlive())循環(huán)函數(shù)哭当,當(dāng)不傳入時(shí)間參數(shù)猪腕,則為永久等待直到線程結(jié)束,傳入時(shí)間參數(shù)钦勘,當(dāng)時(shí)間到達(dá)時(shí)會(huì)結(jié)束join方法

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

        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (millis == 0) {
            while (isAlive()) {
                lock.wait(0);
            }
        } else {
            //循環(huán)陋葡,當(dāng)達(dá)到最大等待時(shí)常,則跳出循環(huán)
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                lock.wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }
}
 public final void join() throws InterruptedException {
    join(0);
}
//等待多少毫秒在加多少納秒
public final void join(long millis, int nanos)
throws InterruptedException {
    synchronized(lock) {
    if (millis < 0) {
        throw new IllegalArgumentException("timeout value is negative");
    }

    if (nanos < 0 || nanos > 999999) {
        throw new IllegalArgumentException(
                            "nanosecond timeout value out of range");
    }

    if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
        millis++;
    }

    join(millis);
    }
}

2.sleep方法

sleep作用是使當(dāng)前線程睡眠指定時(shí)間彻采,其中幾個(gè)關(guān)鍵點(diǎn)

  • 獲取當(dāng)前調(diào)用線程的lock:currentThread().lock;

  • 通過(guò)while (true)循環(huán)sleep當(dāng)前線程腐缤,并檢測(cè)睡眠時(shí)間達(dá)到傳輸參數(shù)時(shí)間,break當(dāng)前循環(huán)

      public static void sleep(long millis, int nanos)throws InterruptedException {
          if (millis < 0) {
              throw new IllegalArgumentException("millis < 0: " + millis);
          }
          if (nanos < 0) {
              throw new IllegalArgumentException("nanos < 0: " + nanos);
          }
          if (nanos > 999999) {
              throw new IllegalArgumentException("nanos > 999999: " + nanos);
          }
    
          //當(dāng)睡眠時(shí)間為0肛响,先檢測(cè)線程是否已經(jīng)中斷岭粤,是的話拋出異常,否則直接return
          if (millis == 0 && nanos == 0) {
              // ...but we still have to handle being interrupted.
              if (Thread.interrupted()) {
                throw new InterruptedException();
              }
              return;
          }
    
          long start = System.nanoTime();
          long duration = (millis * NANOS_PER_MILLI) + nanos;
    
          獲取當(dāng)前線程的lock
          Object lock = currentThread().lock;
    
          // Wait may return early, so loop until sleep duration passes.
          synchronized (lock) {
              while (true) {
                  sleep(lock, millis, nanos);
    
                  long now = System.nanoTime();
                  long elapsed = now - start;
    
                  if (elapsed >= duration) {
                      break;
                  }
    
                  duration -= elapsed;
                  start = now;
                  millis = duration / NANOS_PER_MILLI;
                  nanos = (int) (duration % NANOS_PER_MILLI);
              }
          }
      }
      public static void sleep(long millis) throws InterruptedException {
          Thread.sleep(millis, 0);
      }
    
      @FastNative
      private static native void sleep(Object lock, long millis, int nanos)
          throws InterruptedException;
    

3.sleep與join的區(qū)別

  1. join里面調(diào)用的wait方法特笋,wait方法可以釋放鎖剃浇,而sleep方法是持有鎖
  2. join(0)是一直等待線程執(zhí)行完成,只有這個(gè)線程執(zhí)行完后猎物,才能執(zhí)行其他線程虎囚,中間通過(guò)循環(huán)lock.wait(delay)實(shí)現(xiàn),它是非靜態(tài)方法霸奕,
  3. sleep是靜態(tài)方法,通過(guò)currentThread獲取當(dāng)前線程的lock吉拳,它只能作用當(dāng)前線程

三质帅、Thread終止

1.stop方法

stop方法以及被棄用,強(qiáng)行調(diào)用的話會(huì)拋出UnsupportedOperationException異常

 @Deprecated
public final void stop() {
    stop(new ThreadDeath());
}

  @Deprecated
public final void stop(Throwable obj) {
    throw new UnsupportedOperationException();
}

2.interrupt方法

部分內(nèi)容引用一篇很詳細(xì)的文章留攒,戳-->《Java線程源碼解析之interrupt》

  • interrupt的作用是中斷線程煤惩,我們經(jīng)常調(diào)用,interrupt的使用有幾個(gè)注意點(diǎn)

  • 當(dāng)線程處于wait,sleep,join等方法阻塞狀態(tài)時(shí)炼邀,它會(huì)清除當(dāng)前阻塞狀態(tài)魄揉,并拋出InterruptedException異常

  • 在I/O通訊狀態(tài)中調(diào)用interrupt,數(shù)據(jù)通道會(huì)被關(guān)閉拭宁,并將線程狀態(tài)標(biāo)記為中斷洛退,并拋出ClosedByInterruptException異常

  • 如果在java.nio.channels.Selector上堵塞,會(huì)標(biāo)記中斷狀態(tài)杰标,并馬上返回select方法

  • Lock.lock()方法不會(huì)響應(yīng)中斷兵怯,Lock.lockInterruptibly()方法則會(huì)響應(yīng)中斷并拋出異常,區(qū)別在于park()等待被喚醒時(shí)lock會(huì)繼續(xù)執(zhí)行park()來(lái)等待鎖腔剂,而 lockInterruptibly會(huì)拋出異常

  • synchronized被喚醒后會(huì)嘗試獲取鎖媒区,失敗則會(huì)通過(guò)循環(huán)繼續(xù)park()等待,因此實(shí)際上是不會(huì)被interrupt()中斷的;

  • 一般情況下,拋出異常時(shí)袜漩,會(huì)清空Thread的interrupt狀態(tài)绪爸,在編程時(shí)需要注意;

//用來(lái)中斷的IO通訊對(duì)象宙攻,在調(diào)用interrupt方法后會(huì)調(diào)用blocker的中斷方法
private volatile Interruptible blocker;

public void interrupt() {
    if (this != Thread.currentThread())
        checkAccess();

    synchronized (blockerLock) {
        Interruptible b = blocker;
        if (b != null) {
            nativeInterrupt();
            b.interrupt(this);
            return;
        }
    }
    nativeInterrupt();
}

四奠货、線程的狀態(tài)

public enum State {
    NEW,
    RUNNABLE,
    BLOCKED,
    WAITING,
    TIMED_WAITING,
    TERMINATED;
}

public State getState() {
    // get current thread state
    return State.values()[nativeGetStatus(started)];
}
  • NEW:線程創(chuàng)建還未啟動(dòng)時(shí)狀態(tài)
  • RUNNABLE:線程運(yùn)行狀態(tài),包括一些系統(tǒng)資源等待粘优,如:IO等待仇味,CPU時(shí)間片切換等
  • BLOCKED:正在等待monitor lock的狀態(tài),比如:1. 即將進(jìn)入synchronized方法或者塊前等待獲取鎖的這個(gè)臨界時(shí)期狀態(tài)雹顺。2.調(diào)用wait方法釋放鎖之后再次進(jìn)入synchronized方法或者塊前的臨界狀態(tài)
  • WAITING:基于上個(gè)BLOCKED狀態(tài)來(lái)說(shuō)丹墨,WAITING就是拿到鎖了,處于wait過(guò)程中的狀態(tài)嬉愧,注意它是特指無(wú)限期的等待贩挣,也就是join()或者wait()等,它是join或者直接wait方法當(dāng)獲取到lock執(zhí)行后,處于等待notify的WAITING狀態(tài)没酣。
  • TIMED_WAITING:與上面WAITING相對(duì)王财,WAITING是指無(wú)限期的等待,TIMED_WAITING就是有限期的等待狀態(tài)裕便,包括join(long),wait(long),sleep(long)等绒净。
  • TERMINATED:線程執(zhí)行完成,run結(jié)束的狀態(tài)
    image

五偿衰、總結(jié):回答上述問(wèn)題

  1. 調(diào)用2次start時(shí)挂疆,看start源碼中,里面判斷如果當(dāng)前線程狀態(tài)和是否啟動(dòng)標(biāo)記下翎,if (threadStatus != 0 || started)缤言,如果已經(jīng)啟動(dòng)則拋出IllegalThreadStateException異常,可以通過(guò)繼承Thread類(lèi)或者實(shí)現(xiàn)Runnable去開(kāi)啟線程视事,這樣每次new了新的對(duì)象啟動(dòng)線程
  2. start是啟動(dòng)當(dāng)前Thread線程胆萧,Thread實(shí)現(xiàn)了Runnable接口的run方法,當(dāng)線程啟動(dòng)俐东,run方法會(huì)被調(diào)用跌穗,Thread里面的Run會(huì)調(diào)用傳入Runnable Target的run方法,達(dá)到實(shí)現(xiàn)我們自定義任務(wù)的目的虏辫。如果沒(méi)有傳入Runnable參數(shù)則do nothing
  3. join是等待線程執(zhí)行完成瞻离,方法通過(guò)內(nèi)部一個(gè)while(alive)的循環(huán)函數(shù)去實(shí)現(xiàn)wait等待,alive是一直檢測(cè)線程的存活狀態(tài)乒裆,它相當(dāng)于套利,在那個(gè)線程執(zhí)行join推励,即在哪個(gè)線程執(zhí)行wait,調(diào)用的線程對(duì)象可以理解為lock對(duì)象,即調(diào)用了lock.wait(), sleep方法是一直持有鎖的狀態(tài)肉迫,同時(shí)sleep是靜態(tài)方法验辞,它通過(guò)currentThread獲取當(dāng)前線程的lock,并只能作用當(dāng)前線程
  4. 守護(hù)線程意思是后臺(tái)服務(wù)線程喊衫,比如垃圾回收線程跌造,要理解它就知道另一個(gè)用戶(hù)線程,用戶(hù)線程是維持程序運(yùn)行狀態(tài)族购,或者說(shuō)jvm存活的線程壳贪,如果用戶(hù)線程都跑完了,那么不管守護(hù)線程是否運(yùn)行寝杖,程序和jvm都會(huì)退出违施,當(dāng)然此時(shí),守護(hù)線程也會(huì)退出瑟幕,由此可以看出守護(hù)線程和用戶(hù)線程對(duì)于程序運(yùn)行的相關(guān)性磕蒲。由上述線程的init方法可以看出,子線程的創(chuàng)建會(huì)繼承一些默認(rèn)參數(shù)只盹,包含是否為守護(hù)線程辣往,它是低級(jí)別的線程,不依賴(lài)于終端殖卑,但是依賴(lài)于系統(tǒng)站削,與系統(tǒng)“同生共死”。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末孵稽,一起剝皮案震驚了整個(gè)濱河市许起,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌肛冶,老刑警劉巖街氢,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件扯键,死亡現(xiàn)場(chǎng)離奇詭異睦袖,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)荣刑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)馅笙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人厉亏,你說(shuō)我怎么就攤上這事董习。” “怎么了爱只?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵皿淋,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng)窝趣,這世上最難降的妖魔是什么疯暑? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮哑舒,結(jié)果婚禮上妇拯,老公的妹妹穿的比我還像新娘。我一直安慰自己洗鸵,他們只是感情好越锈,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著膘滨,像睡著了一般甘凭。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上吏祸,一...
    開(kāi)封第一講書(shū)人閱讀 51,631評(píng)論 1 305
  • 那天对蒲,我揣著相機(jī)與錄音,去河邊找鬼贡翘。 笑死蹈矮,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的鸣驱。 我是一名探鬼主播泛鸟,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼踊东!你這毒婦竟也來(lái)了北滥?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤闸翅,失蹤者是張志新(化名)和其女友劉穎再芋,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體坚冀,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡济赎,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年辜御,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了歼郭。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片疾呻。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡溉委,死狀恐怖嫉入,靈堂內(nèi)的尸體忽然破棺而出浪讳,到底是詐尸還是另有隱情虹曙,我是刑警寧澤氓癌,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布滑凉,位于F島的核電站统扳,受9級(jí)特大地震影響喘帚,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜咒钟,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一啥辨、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧盯腌,春花似錦溉知、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至帚湘,卻和暖如春玫荣,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背大诸。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工捅厂, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人资柔。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓焙贷,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親贿堰。 傳聞我的和親對(duì)象是個(gè)殘疾皇子辙芍,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

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

  • 進(jìn)程和線程 進(jìn)程 所有運(yùn)行中的任務(wù)通常對(duì)應(yīng)一個(gè)進(jìn)程,當(dāng)一個(gè)程序進(jìn)入內(nèi)存運(yùn)行時(shí),即變成一個(gè)進(jìn)程.進(jìn)程是處于運(yùn)行過(guò)程中...
    勝浩_ae28閱讀 5,110評(píng)論 0 23
  • 進(jìn)程和線程 進(jìn)程 所有運(yùn)行中的任務(wù)通常對(duì)應(yīng)一個(gè)進(jìn)程,當(dāng)一個(gè)程序進(jìn)入內(nèi)存運(yùn)行時(shí),即變成一個(gè)進(jìn)程.進(jìn)程是處于運(yùn)行過(guò)程中...
    小徐andorid閱讀 2,810評(píng)論 3 53
  • 前言 昨天已經(jīng)寫(xiě)了: 多線程三分鐘就可以入個(gè)門(mén)了! 如果沒(méi)看的同學(xué)建議先去閱讀一遍哦~ 在寫(xiě)文章之前通讀了一遍《J...
    Java3y閱讀 993評(píng)論 1 10
  • Java多線程學(xué)習(xí) [-] 一擴(kuò)展javalangThread類(lèi) 二實(shí)現(xiàn)javalangRunnable接口 三T...
    影馳閱讀 2,959評(píng)論 1 18
  • 本文主要講了java中多線程的使用方法羹与、線程同步故硅、線程數(shù)據(jù)傳遞、線程狀態(tài)及相應(yīng)的一些線程函數(shù)用法纵搁、概述等吃衅。 首先講...
    李欣陽(yáng)閱讀 2,456評(píng)論 1 15