Java線程

Java線程基礎

線程狀態

在Thread.java類文件中,有一個state靜態枚舉內部類,預定義了Thread的狀態。

State Name Dec
NEW 線程未開啟
RUNNABLE 線程可運行狀態,一個可運行的線程在JVM中運行,但有可能因為處理器等操作系統的原因處于等待
BLOCKED 阻塞狀態,處于阻塞狀態的線程,等待監視器的鎖。例如調用Object.wait()后,線程處于阻塞,等待獲取監視器的鎖進入同步代碼塊、方法或重新進入同步代碼塊、方法。
WAITING 等待狀態,一個等待狀態的線程等待另一個線程去執行特定的操作。例如一個線程使用了Thread.join(),該線程等待指定線程終結;一個線程調用了一個對象的wait(),等待另一個線程調用這個對象的notify()/notifyAll()喚醒等待的線程。
TIMED_WAITING 線程等待指定的時間。
TERMINATED 線程被終止,線程完成任務。

BLOCKED&WAITING

在文檔中介紹,BOLCKED狀態主要指多線程程序運行時,多個線程進入臨界區,線程互相排斥,等待鎖的獲取與釋放。而WAITING泛指處于等待狀態的線程等待另一個線程去執行任務。

WAITING狀態是因為Object.wait(),Thread.join()等方法(無參重載方法,不需要指定時間)造成的。而BLOCKED主要是因為同步中鎖的獲取與釋放以及IO流阻塞方法。

WAITING&TIMED_WAITING

WAITING與TIMED_WAITHING的區別在于WAITING需要手動喚醒,例如調用Object.notify()/Object.notifyAll();而TIMED_WAITING狀態是線程等待一定的時間。

造成TIMED_WAITING狀態的原因可能是調用了以下方法:

  • Thread.sleep()
  • Object.wait()的指定時間重載方法
  • Thread.join()的指定時間重載方法
  • 等等

jstack

使用JDK內置工具jstack可以查看線程處于什么狀態。一般的jstack查看命令jstack pid。而pid是指Java虛擬機的進程id,可以通過jps命令來查看。

了解了線程狀態,以及他們的原因和解決辦法,遇到問題時就有頭緒解決。例如線程處于阻塞狀態,可能是因為臨界區的鎖沒有釋放等。

更多JDK內置工具可以參考 jstack命令(Java Stack Trace)

參考

Java線程狀態分析

Lock

一般實現同步,都是給代碼臨界區加上synchronized,變成同步語句或同步方法。這樣保證線程排斥,同一時刻只允許一個線程進入代碼臨界區。實際上,執行同步方法或語句之前,線程隱式的給實例(或類)加了一個鎖。在Java中可以顯示加鎖。

鎖的作用

Lock實際上創建的是相互排斥的鎖。當線程執行臨界區時,首先通過lock.lock()獲取鎖,執行完畢后通過lock.unlock()釋放鎖,讓其他獲取到鎖的線程有機會執行。由于是排斥的鎖,所以同一時刻只有一個線程可以進入代碼臨界區。

代碼編寫過程中,一般使用try-finally,在finally塊中釋放鎖。

線程協作

由于線程的調度完全由CPU分配(線程共享CPU時間切片),導致程序無法按照開發人員設計運行。所以線程之間的協作尤為重要。

可以通過Lock.newCondition()創建一個條件對象(Condition)。線程間的協作需要通信,通過Condition對象線程間相互通信,指定線程在條件下該做何種操作。

創建條件實例后,可以通過以下方法實現線程通信:

  • await(),使線程進入WAITING狀態,直到執行條件發生,通過手動喚醒;
  • signal(),條件滿足,喚醒該條件等待的一條線程;
  • signalAll(),條件滿足,喚醒該條件等待的所有線程。

注意,使用條件的方法前,必須已經獲取了鎖,否則會拋出異常。使用await()進入等待的線程,必須使用喚醒方法,否則一直處于等待(WAITING)狀態。

await(),可以讓當前鎖定臨界區的線程釋放該條件的鎖,允許其他線程進入。而且當使用signal()/signalAll()喚醒等待線程后,執行先前使用await()的下一行代碼。

監視器

鎖和條件都是Java 5中新增的內容,在此之前,線程之間的通信是通過對象內置的監視器編程實現的。

監視器是一個相互排斥且具備同步能力的對象。監視器中的一個時間點上,只能有一個線程執行一個方法。

線程通過獲取監視器上的鎖進入監視器,并且通過釋放鎖退出監視器。而這個鎖是通過線程執行synchronized方法塊或方法來獲取的。也就是說監視器對象可以是任意對象,但必須是synchronized鎖住的對象。

通過調用監視器對象的wait()來釋放鎖并且進入等待狀態,讓監視器對象中的其它線程有機會執行任務;當條件合適時,另一個線程中調用監視器的notify()/notifyAll()來通知一個或所有等待線程重新獲取鎖并且恢復執行。

當監視器調用wait()時,阻塞當前線程,并且釋放鎖。當監視器調用notify()/notifyAll()時,通知該線程啟動,并且可以獲取鎖。

Object的wait()、notify()、notifyAll()類似于Condition的await()、signal()、signalAll()。

死鎖

又是兩個線程或多個線程需要在幾個共享對象上獲取鎖,這就有可能導致死鎖。

例如,線程a獲取了對象A的鎖,并且在等待對象B的鎖;而此時線程b獲取了對象B的鎖,在等待對象A的鎖。這樣一來線程a,b都無法獲取到需要的鎖繼續執行任務(沒有線程釋放所需的鎖),導致死鎖。

解決辦法是通過資源排序技術。該技術指定給對象上鎖順序,避免死鎖。

例如指定對象A,B上鎖的順序必須是A在前,那么線程a,b就不會死鎖,因為線程b必須先獲取對象A的鎖才可以獲取對象B的鎖,否則一直處于BLOCKED。

一個線程從一個共享對象獲取鎖后,此時有另一個線程獲取該對象的鎖時,由于鎖的互斥,第二個線程無法執行。

中斷線程

終止線程有兩個原因:

  • 線程的run()執行到最后一條語句,并執行return語句返回時,自然終止;
  • 線程的run()執行時遇到一個沒有捕獲的異常意外終止。

沒有可以強制終止線程的方法(stop()被廢棄),但是可以使用interrupt()來請求終止線程,當調用它時,線程的中斷狀態被置位。這是每一個線程都有一個boolean標志位,且應該不時地去檢查這個標志位,以判斷線程是否被中斷。

沒有強制線程終止的方法,但是通過interrupt()方法,可以將中斷狀態標志位置位,然后去檢查它,來判斷線程是否被中斷(實際上線程未終止,可以根據標志位去執行特定的操作)。

當一個線程在調用interrupt()后,被置于阻塞狀態(阻塞調用后進入阻塞狀態,sleep或wait等方法的執行),就會拋出InterruptedException異常,中斷阻塞調用。

線程的中斷非終止。任何語言都沒有需求要求一個被中斷的線程應該被終止(置線程于TERMINATED狀態)。

中斷線程是引起線程的注意,被中斷的線程決定如何響應中斷,保證了線程在安全的時候停止。普遍的處理是線程將中斷作為一個終止請求。

public void run() {
    try {
        ...
        while(!Thread.currentThread().isInterrupted()  //check interrput boolean
            && more work to do) {
            do more work
        }
    }catch(InterruptedException) {
        //Thread was interrupted during sleep or wait
    }finally {
        cleanup,if required //do something for terminated
    }
    //exiting the run method terminates the thread
}

中斷方法

  • void interrupt(),請求中斷,中斷標志位置位。
  • static boolean interrupted(),靜態方法,檢測當前線程是否被中斷(其實代碼就是檢測中斷標志位),且清楚該線程的中斷標志位值。如果被中斷,返回TRUE,否則返回FALSE。
  • boolean isInterrupted(),用來檢測是否有線程被中斷,但是不會清楚中斷狀態。如果被中斷,返回TRUE,否則返回FALSE。

GUI線程

Java的GUI事件處理和繪制任務在一個event dispatch thread中執行。這是因為大多數GUI方法都是非線程安全的,如果從多線程中執行這些任務會造成沖突。

線程安全,如果一段代碼在多線程程序中沒有導致競爭狀態,則稱這樣的代碼是線程安全的。

可以通過javax.swing.Utilities類中的靜態方法,在event dispatch thread中執行任務。

  • Utilities.invokeLater(),該方法無須等待任務執行完畢就返回。
  • Utilities.invokeAndWait(),該方法必須等待任務執行完畢后返回,導致方法阻塞。

信號量

信號量可以用來限制訪問共享資源的線程數量。在訪問資源之前,線程從信號量獲取許可,此時總的許可減一;在訪問完資源后,線程必須把許可釋放還給信號量。

當信號量的許可總數為1時,可以實現同步。

Thread.sleep()&Object.wait()

在以前的JDK版本中還有一個suspend()實例方法,用于阻塞線程。但是它極其容易導致死鎖,擁有鎖的線程被掛起后,在恢復之前(resume())無法釋放鎖。如果一個線程調用另一個線程suspend(),且該線程需要獲取同一個鎖時,就造成了死鎖。

Thread.sleep()也不會釋放鎖,直接阻塞線程。但是由于它是靜態方法,所以阻塞的是當前線程,不會造成死鎖。

Condition、監視器的阻塞線程的方法可以釋放鎖,當等待中的線程被喚醒時又有機會重新獲取鎖。

所以他們的主要區別有:

  1. 它們來自不同的類,同時sleep()屬于靜態方法,使當前線程進入TIMED_WAITING。
  2. sleep()不會釋放鎖,而wait()會。
  3. sleep()可以在線程中的任意地方調用,而wait()必須在同步方法或同步語句中調用。
  4. sleep()必須捕獲InterruptedExpection,而wait()不需要。

鎖的可重入

鎖的可重入是指,當一個線程試圖請求獲取已經持有的鎖時,這個請求可以成功。重入的一個重要作用是防止死鎖:

public class Father {
    public synchronized void doSomething(){ ......
    } 
}

public class Child extends Father {
    public synchronized void doSomething(){ ......
        super.doSomething(); 
    }
}

當一個線程調用Child實例的doSomething()時,首先請求獲取該實例的鎖,在執行super.doSomething()時再次請求獲取實例的鎖。

如果沒有重入,當調用super.doSomething()時無法再次獲取Child實例鎖,線程會一直阻塞下去,造成死鎖。而鎖的重入就避免了這種死鎖。

但是當退出super.soSomething()時,線程并未釋放鎖,只有退出Child實例的doSomething()時,才會釋放鎖,讓其他線程進入。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,908評論 6 541
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,324評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,018評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,675評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,417評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,783評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,779評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,960評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,522評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,267評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,471評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,009評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,698評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,099評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,386評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,204評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,436評論 2 378

推薦閱讀更多精彩內容

  • 下面是我自己收集整理的Java線程相關的面試題,可以用它來好好準備面試。 參考文檔:-《Java核心技術 卷一》-...
    阿呆變Geek閱讀 14,884評論 14 507
  • 前言- CPU競爭策略 操作系統中,CPU競爭有很多種策略。Unix系統使用的是時間片算法,而Windows則屬于...
    zhanglbjames閱讀 456評論 1 1
  • 一、進程和線程 進程 進程就是一個執行中的程序實例,每個進程都有自己獨立的一塊內存空間,一個進程中可以有多個線程。...
    阿敏其人閱讀 2,620評論 0 13
  • 不管你是新程序員還是老手,你一定在面試中遇到過有關線程的問題。Java語言一個重要的特點就是內置了對并發的支持,讓...
    堯淳閱讀 1,608評論 0 25
  • 年前拍攝的一個十天的小女孩 和媽媽一樣白皙的肌膚 安靜,而甜美 擁有爸爸無盡的憐愛,疼惜。
    攝影師李瑕Melissa閱讀 269評論 3 7