Java 線程:創(chuàng)建巾表、屬性以及狀態(tài)控制

前言


本文將對(duì) Java 線程 Thread 進(jìn)行學(xué)習(xí)和總結(jié),以下是概覽:

目錄

一唠摹、Thread 創(chuàng)建

線程的創(chuàng)建主要依靠實(shí)現(xiàn) Runnable 接口。調(diào)用 start() 方法使線程進(jìn)入就緒狀態(tài)奉瘤,等待 CPU 調(diào)度勾拉,然后 run () 方法由 JVM 調(diào)用。

1.1 實(shí)現(xiàn) Runnable

public interface Runnable {
    public abstract void run();
}

例子

public class TestThread implements Runnable{

    @Override
    public void run() {
        System.out.println("TestThread Running");
    }
}

使用

public static void main(String[] args) {
     // 創(chuàng)建線程
     TestThread testThread = new TestThread();
     // 將 Runnable 對(duì)象作為參數(shù)傳遞給 Thread
     Thread thread = new Thread(testThread);
     thread.start();
}

為什么不能直接創(chuàng)建 TestThread 對(duì)象并調(diào)用 run() 方法呢盗温,因?yàn)檫@樣只是普通地調(diào)用了對(duì)象的方法藕赞,并沒有經(jīng)歷創(chuàng)建線程的過程。

所以還是需要利用 Java 為我們寫好的 Thread 類對(duì)線程對(duì)象(實(shí)現(xiàn)了 Runnable 的對(duì)象)進(jìn)行一次包裝卖局,然后由 Thread 類替我們完成創(chuàng)建并執(zhí)行線程的過程斧蜕。

1.2 繼承 Thread

Thread 類本身實(shí)現(xiàn)了 Runnable 接口,內(nèi)部又包裝了一個(gè) Thread 對(duì)象 target砚偶,執(zhí)行 run 方法的時(shí)候?qū)嶋H調(diào)用 targetrun 方法批销。

public class Thread implements Runnable {
    private Runnable target;
    ...
    public void run() {
        if (target != null) {
            target.run();
        }
    }
}

所以本質(zhì)上還是實(shí)現(xiàn)了 Runnable 接口實(shí)現(xiàn)線程的創(chuàng)建。

二染坯、部分屬性


Thread 類的部分屬性:

// 對(duì)象鎖均芽,用于使當(dāng)前線程占有 CPU
private final Object lock = new Object();
// 該值不等于0,說明線程存活酒请,尚不知原理
private volatile long nativePeer;
// 線程名稱
private volatile String name;
// 線程優(yōu)先級(jí)
private int         priority;
// 是否守護(hù)線程
private boolean     daemon = false;

接下來將逐個(gè)分析。

2.1 lock 鎖對(duì)象

lock 對(duì)象用于加鎖和使調(diào)用者線程進(jìn)入阻塞狀態(tài)鸣个。

阻塞

調(diào)用 join 方法會(huì)使當(dāng)前線程獲取 lock 對(duì)象的鎖羞反,然后 lock 對(duì)象執(zhí)行 wait 方法進(jìn)入阻塞狀態(tài),實(shí)現(xiàn)阻塞當(dāng)前線程的效果囤萤。

public final void join() throws InterruptedException {
    // 傳入 0 表示無限阻塞
    join(0);
}

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) { // 等于 0 無限阻塞
        while (isAlive()) {
            lock.wait(0);
        }
    } else {
        while (isAlive()) {
            long delay = millis - now;
            if (delay <= 0) {
                break;
            }
            lock.wait(delay);
            now = System.currentTimeMillis() - base;
        }
    }
    }
}

比如現(xiàn)在有個(gè)線程 Thread1昼窗,在運(yùn)行過程中需要執(zhí)行 Thread2 的相關(guān)邏輯。Thread2Thread1run() 方法中調(diào)用了 join() 方法涛舍,這樣 Thread1 就獲取到了 Thread2lock 對(duì)象的鎖澄惊。

lock 對(duì)象循環(huán)調(diào)用 wait() 方法進(jìn)行阻塞。因?yàn)檫@時(shí)是處于 Thread1 的運(yùn)行環(huán)境下富雅,并且由 synchronized 獲取了 monitor(相當(dāng)于同步鎖)掸驱,所以 Thread1 進(jìn)入了阻塞狀態(tài)。

Java Object wait() 方法

注意:

當(dāng)前線程必須是此對(duì)象的監(jiān)視器所有者没佑,否則還是會(huì)發(fā)生 IllegalMonitorStateException 異常毕贼。
如果當(dāng)前線程在等待之前或在等待時(shí)被任何線程中斷,則會(huì)拋出 InterruptedException 異常蛤奢。

解除阻塞

解除阻塞有兩種方式鬼癣,但不限于這兩種陶贼。一種是 超時(shí) 自動(dòng)釋放,另一種是由 JVM 釋放待秃。

  • join() 方法傳入 millis (毫秒數(shù))拜秧,在時(shí)間達(dá)到之后跳出循環(huán),lock 對(duì)象不再加鎖章郁。這樣 Thread1 持有的 lock 對(duì)象不再阻塞枉氮,由此該線程回到就緒狀態(tài)。
  • 加入線程執(zhí)行完畢驱犹,JVM 釋放鎖嘲恍。線程死亡時(shí) JVM 會(huì)調(diào)用 lock 對(duì)象的 notify_all() 方法來釋放所有鎖。

以下代碼摘自 Thread.join的作用和原理 中的 hotspot 虛擬機(jī)源碼:

void JavaThread::exit(bool destroy_vm, ExitType exit_type) {
  assert(this == JavaThread::current(),  "thread consistency check");
  ...
  // Notify waiters on thread object. This has to be done after exit() is called
  // on the thread (if the thread is the last thread in a daemon ThreadGroup the
  // group should have the destroyed bit set before waiters are notified).
  ensure_join(this); 
  assert(!this->has_pending_exception(), "ensure_join should have cleared");
  ...

可以看到線程退出方法 exit() 調(diào)用了 ensure_join(this) 釋放鎖:

static void ensure_join(JavaThread* thread) {
  // We do not need to grap the Threads_lock, since we are operating on ourself.
  Handle threadObj(thread, thread->threadObj());
  assert(threadObj.not_null(), "java thread object must exist");
  ObjectLocker lock(threadObj, thread);
  // Ignore pending exception (ThreadDeath), since we are exiting anyway
  thread->clear_pending_exception();
  // Thread is exiting. So set thread_status field in  java.lang.Thread class to TERMINATED.
  java_lang_Thread::set_thread_status(threadObj(), java_lang_Thread::TERMINATED);
  // Clear the native thread instance - this makes isAlive return false and allows the join()
  // to complete once we've done the notify_all below
  //這里是清除native線程雄驹,這個(gè)操作會(huì)導(dǎo)致isAlive()方法返回false
  java_lang_Thread::set_thread(threadObj(), NULL);
  lock.notify_all(thread);//注意這里
  // Ignore pending exception (ThreadDeath), since we are exiting anyway
  thread->clear_pending_exception();
}

這里的 lock 對(duì)象大概就是 Thread 中的同步鎖對(duì)象了佃牛,調(diào)用 Java Object notifyAll() 方法 用來喚醒所有持有該對(duì)象鎖的線程。

2.2 其它參數(shù)

  • nativePeer: 判斷線程是否存活依靠此字段医舆,可能在線程銷毀的時(shí)候把該值置為 0俘侠。
public final boolean isAlive() {
    return nativePeer != 0;
}
  • priority: 優(yōu)先級(jí),默認(rèn) NORM_PRIORITY=5蔬将。最小 MIN_PRIORITY = 1爷速,最大 MAX_PRIORITY = 10
    設(shè)置該參數(shù)的作用在于希望較高優(yōu)先級(jí)的線程可以先行執(zhí)行霞怀,但實(shí)際可能并非總是如此惫东。

優(yōu)先級(jí)和操作系統(tǒng)及虛擬機(jī)版本相關(guān)。
優(yōu)先級(jí)只是代表告知了 「線程調(diào)度器」該線程的重要度有多大毙石。如果有大量線程都被堵塞廉沮,都在等候運(yùn)
行,調(diào)試程序會(huì)首先運(yùn)行具有最高優(yōu)先級(jí)的那個(gè)線程徐矩。然而滞时,這并不表示優(yōu)先級(jí)較低的線程不會(huì)運(yùn)行(換言之,不會(huì)因?yàn)榇嬖趦?yōu)先級(jí)而導(dǎo)致死鎖)滤灯。若線程的優(yōu)先級(jí)較低坪稽,只不過表示它被準(zhǔn)許運(yùn)行的機(jī)會(huì)小一些而已。

  • daemon: 是否為守護(hù)線程鳞骤。守護(hù)線程就像一個(gè)衛(wèi)士窒百,追隨用戶線程。當(dāng)用戶線程銷毀時(shí)豫尽,守護(hù)線程也就沒有了意義贝咙,可能會(huì)被隨時(shí)回收柔袁。

參考:什么是守護(hù)線程檐涝?

三喧伞、Thread 狀態(tài)


3.1 線程的幾種狀態(tài)

有關(guān)線程狀態(tài)纸泡,大概有兩種說法。

  • 一種是概括性的五種狀態(tài):
    • 創(chuàng)建狀態(tài) 已經(jīng)創(chuàng)建出對(duì)象蔼水,并未調(diào)用 start震糖;
    • 就緒狀態(tài) 調(diào)用 start 方法之后,并未開始運(yùn)行趴腋;
    • 運(yùn)行狀態(tài) 被 cpu 執(zhí)行吊说,run 方法被調(diào)用;
    • 阻塞狀態(tài) 在運(yùn)行過程中被暫停优炬,調(diào)用 wait() 或被別的線程 join() 等颁井;
    • 死亡狀態(tài) run 方法執(zhí)行結(jié)束,或者調(diào)用 stop() 結(jié)束運(yùn)行蠢护。
  • 另一種是根據(jù)源碼的六種狀態(tài):
    • NEW 剛 new 出來雅宾;
    • RUNNABLE 運(yùn)行中;
    • BLOCKED 等待同步鎖葵硕;
    • WAITING 阻塞狀態(tài)眉抬,wait() 或被 join();
    • TIMED_WAITING 阻塞狀態(tài)懈凹,限時(shí)蜀变;
    • TERMINATED 被終止。

個(gè)人感覺沒有必要糾結(jié)孰對(duì)孰錯(cuò)介评,這是兩個(gè)層面上的理解库北。前者更傾向于總覽,后者更為細(xì)化们陆。

3.2 線程狀態(tài)控制

線程調(diào)度

簡(jiǎn)單地說就是設(shè)置優(yōu)先級(jí)寒瓦,使 JVM 進(jìn)行協(xié)調(diào)。避免多個(gè)線程搶奪有限資源造成的死機(jī)或者崩潰棒掠。上文已經(jīng)提到過孵构,最低優(yōu)先級(jí) MIN_PRIORITY =1屁商、最高優(yōu)先級(jí) MAX_PRIORITY = 10烟很,默認(rèn)優(yōu)先級(jí) NORM_PRIORITY=5

為了控制線程的運(yùn)行策略蜡镶,Java定義了線程調(diào)度器來監(jiān)控系統(tǒng)中處于就緒狀態(tài)的所有線程雾袱。線程調(diào)度器按照線程的優(yōu)先級(jí)決定那個(gè)線程投入處理器運(yùn)行。在多個(gè)線程處于就緒狀態(tài)的條件下官还,具有高優(yōu)先級(jí)的線程會(huì)在低優(yōu)先級(jí)線程之前得到執(zhí)行芹橡。

守護(hù)線程

特殊的低優(yōu)先級(jí)守護(hù)(Daemon)線程,它是為系統(tǒng)中的其它對(duì)象或線程服務(wù)望伦。

典型的守護(hù)線程例子是JVM中的系統(tǒng)資源自動(dòng)回收線程林说,它始終在低級(jí)別的狀態(tài)中運(yùn)行煎殷,用于實(shí)時(shí)監(jiān)控和管理系統(tǒng)中的可回收資源。

當(dāng)所有用戶線程銷毀后腿箩,也不會(huì)產(chǎn)生垃圾了豪直。守護(hù)線程會(huì)隨著 JVM 銷毀。

線程分組

Java定義了在多線程運(yùn)行系統(tǒng)中的線程組(ThreadGroup)對(duì)象珠移,用于實(shí)現(xiàn)按照特定功能對(duì)線程進(jìn)行集中式分組管理弓乙。

用戶創(chuàng)建的每個(gè)線程均屬于某線程組,這個(gè)線程組可以在線程創(chuàng)建時(shí)指定钧惧,也可以不指定線程組以使該線程處于默認(rèn)的線程組之中暇韧。但是,一旦線程加入某線程組浓瞪,該線程就一直存在于該線程組中直至線程死亡懈玻,不能在中途改變線程所屬的線程組。

與線程類似追逮,可以針對(duì)線程組對(duì)象進(jìn)行線程組的調(diào)度酪刀、狀態(tài)管理以及優(yōu)先級(jí)設(shè)置等。在對(duì)線程組進(jìn)行管理過程中钮孵,加入到某線程組中的所有線程均被看作統(tǒng)一的對(duì)象骂倘。

四、Thread 其它


4.1 線程同步

  • synchronized: 依賴 JVM 實(shí)現(xiàn)巴席,通過鎖住 對(duì)象历涝、方法、類 等實(shí)現(xiàn)線程同步漾唉;
  • volatile: 特殊域變量荧库,使用該關(guān)鍵字修飾對(duì)象,保證其 可見性赵刑。使其無論在哪個(gè)線程讀取時(shí)都從內(nèi)存重新讀取分衫,而不是在 CPU 緩存中讀取般此;
  • 重入鎖: ReentrantLock 可重入蚪战,內(nèi)含 CAS+AQS 機(jī)制。CAS 保證數(shù)據(jù)準(zhǔn)確性铐懊,AQS 保證順序邀桑。
  • ThreadLocal: 為每一條線程創(chuàng)建數(shù)據(jù)副本,這樣各個(gè)線程間處理的數(shù)據(jù)互不影響科乎。因?yàn)樘幚淼牟皇峭粩?shù)據(jù)壁畸,要注意數(shù)據(jù)的一致性。

4.2 終止線程

終止線程三種方法:

  • 設(shè)置 flag 標(biāo)記:為 false 時(shí)結(jié)束代碼運(yùn)行,JVM 會(huì)回收掉線程捏萍;
  • stop 方法(不推薦):強(qiáng)行結(jié)束線程太抓,但是可能會(huì)因馬上釋放鎖造成數(shù)據(jù)產(chǎn)生誤差;
  • interrupt 方法:使線程進(jìn)入中斷狀態(tài)令杈,代碼邏輯中 catch InterruptedException 用來結(jié)束邏輯執(zhí)行腻异。

總結(jié)

線程使用時(shí)要特別注意同步、鎖的使用这揣。由于多個(gè)線程不易管理悔常,實(shí)際使用時(shí)一般用 線程池 進(jìn)行處理。后面講對(duì)線程池原理進(jìn)行記錄给赞。

參考資料

線程的狀態(tài)控制

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末机打,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子片迅,更是在濱河造成了極大的恐慌残邀,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件柑蛇,死亡現(xiàn)場(chǎng)離奇詭異芥挣,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)耻台,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門空免,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人盆耽,你說我怎么就攤上這事蹋砚。” “怎么了摄杂?”我有些...
    開封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵坝咐,是天一觀的道長。 經(jīng)常有香客問我析恢,道長墨坚,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任映挂,我火速辦了婚禮泽篮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘袖肥。我一直安慰自己咪辱,他們只是感情好振劳,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開白布椎组。 她就那樣靜靜地躺著,像睡著了一般历恐。 火紅的嫁衣襯著肌膚如雪寸癌。 梳的紋絲不亂的頭發(fā)上专筷,一...
    開封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音蒸苇,去河邊找鬼磷蛹。 笑死,一個(gè)胖子當(dāng)著我的面吹牛溪烤,可吹牛的內(nèi)容都是我干的味咳。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼檬嘀,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼槽驶!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起鸳兽,我...
    開封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤掂铐,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后揍异,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體全陨,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年衷掷,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了辱姨。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡戚嗅,死狀恐怖炮叶,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情渡处,我是刑警寧澤镜悉,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站医瘫,受9級(jí)特大地震影響侣肄,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜醇份,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一稼锅、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧僚纷,春花似錦矩距、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春哮肚,著一層夾襖步出監(jiān)牢的瞬間登夫,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來泰國打工允趟, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留恼策,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓潮剪,卻偏偏與公主長得像涣楷,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子抗碰,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

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