線程是一個獨立執行的調用序列,同一個進程的線程在同一時刻共享一些系統資源(好比文件句柄等)也能訪問同一個進程所建立的對象資源(內存資源)。java.lang.Thread對象負責統計和控制這種行爲。java
每一個程序都至少擁有一個線程-即做爲Java虛擬機(JVM)啓動參數運行在主類main方法的線程。在Java虛擬機初始化過程當中也可能啓動其餘的後臺線程。這種線程的數目和種類因JVM的實現而異。然而全部用戶級線程都是顯式被構造並在主線程或者是其餘用戶線程中被啓動。編程
這裏對Thread類中的主要方法和屬性以及一些使用注意事項做出總結。這些內容會在這本書(《Java Concurrency Constructs》)上進行進一步的討論闡述。Java語言規範以及已發佈的API文檔中都會有更詳細權威的描述。數組
Thread類中不一樣的構造方法接受以下參數的不一樣組合:安全
一個Runnable對象,這種狀況下,Thread.start方法將會調用對應Runnable對象的run方法。若是沒有提供Runnable對象,那麼就會當即獲得一個Thread.run的默認實現。 一個做爲線程標識名的String字符串,該標識在跟蹤和調試過程當中會很是有用,除此別無它用。 線程組(ThreadGroup),用來放置新建立的線程,若是提供的ThreadGroup不容許被訪問,那麼就會拋出一個SecurityException 。 Thread類自己就已經實現了Runnable接口,所以,除了提供一個用於執行的Runnable對象做爲構造參數的辦法以外,也能夠建立一個Thread的子類,經過重寫其run方法來達到一樣的效果。然而,比較好的實踐方法倒是分開定義一個Runnable對象並用來做爲構造方法的參數。將代碼分散在不一樣的類中使得開發人員無需糾結於Runnable和Thread對象中使用的同步方法或同步塊之間的內部交互。更廣泛的是,這種分隔使得對操做的自己與其運行的上下文有着獨立的控制。更好的是,同一個Runnable對象能夠同時用來初始化其餘的線程,也能夠用於構造一些輕量化的執行框架(Executors)。另外須要提到的是經過繼承Thread類實現線程的方式有一個缺點:使得該類沒法再繼承其餘的類。多線程
Thread對象擁有一個守護(daemon)標識屬性,這個屬性沒法在構造方法中被賦值,可是能夠在線程啓動以前設置該屬性(經過setDaemon方法)。當程序中全部的非守護線程都已經終止,調用setDaemon方法可能會致使虛擬機粗暴的終止線程並退出。isDaemon方法可以返回該屬性的值。守護狀態的做用很是有限,即便是後臺線程在程序退出的時候也常常須要作一些清理工做。(daemon的發音爲」day-mon」,這是系統編程傳統的遺留,系統守護進程是一個持續運行的進程,好比打印機隊列管理,它老是在系統中運行。)併發
調用start方法會觸發Thread實例以一個新的線程啓動其run方法。新線程不會持有調用線程的任何同步鎖。app
當一個線程正常地運行結束或者拋出某種未檢測的異常(好比,運行時異常(RuntimeException),錯誤(ERROR) 或者其子類)線程就會終止。當線程終止以後,是不能被從新啓動的。在同一個Thread上調用屢次start方法會拋出InvalidThreadStateException異常。框架
若是線程已經啓動可是尚未終止,那麼調用isAlive方法就會返回true.即便線程因爲某些緣由處於阻塞(Blocked)狀態該方法依然返回true。若是線程已經被取消(cancelled),那麼調用其isAlive在何時返回false就因各Java虛擬機的實現而異了。沒有方法能夠得知一個處於非活動狀態的線程是否已經被啓動過了(譯者注:即線程在開始運行前和結束運行後都會返回false,你沒法得知處於false的線程具體的狀態)。另外一點,雖然一個線程可以得知同一個線程組的其餘線程的標識,可是卻沒法得知本身是由哪一個線程調用啓動的。dom
Java虛擬機爲了實現跨平臺(不一樣的硬件平臺和各類操做系統)的特性,Java語言在線程調度與調度公平性上未做出任何的承諾,甚至都不會嚴格保證線程會被執行。可是Java線程卻支持優先級的方法,這些方法會影響線程的調度:this
每一個線程都有一個優先級,分佈在Thread.MIN_PRIORITY和Thread.MAX_PRIORITY之間(分別爲1和10) 默認狀況下,新建立的線程都擁有和建立它的線程相同的優先級。main方法所關聯的初始化線程擁有一個默認的優先級,這個優先級是Thread.NORM_PRIORITY (5). 線程的當前優先級能夠經過getPriority方法得到。 線程的優先級能夠經過setPriority方法來動態的修改,一個線程的最高優先級由其所在的線程組限定。
當可運行的線程數超過了可用的CPU數目的時候,線程調度器更偏向於去執行那些擁有更高優先級的線程。具體的策略因平臺而異。好比有些Java虛擬機實現老是選擇當前優先級最高的線程執行。有些虛擬機實現將Java中的十個優先級映射到系統所支持的更小範圍的優先級上,所以,擁有不一樣優先級的線程可能最終被同等對待。還有些虛擬機會使用老化策略(隨着時間的增加,線程的優先級逐漸升高)動態調整線程優先級,另外一些虛擬機實現的調度策略會確保低優先級的線程最終仍是可以有機會運行。設置線程優先級能夠影響在同一臺機器上運行的程序之間的調度結果,可是這不是必須的。
線程優先級對語義和正確性沒有任何的影響。特別是,優先級管理不能用來代替鎖機制。優先級僅僅是用來代表哪些線程是重要緊急的,當存在不少線程在激勵進行CPU資源競爭的狀況下,線程的優先級標識將會顯得很是有用。好比,在ParticleApplet中將particle animation線程的優先級設置的比建立它們的applet線程低,在某些系統上可以提升對鼠標點擊的響應,並且不會對其餘功能形成影響。可是即便setPriority方法被定義爲空實現,程序在設計上也應該保證可以正確執行(儘管可能會沒有響應)。
下面這個表格列出不一樣類型任務在線程優先級設定上的一般約定。在不少併發應用中,在任一指定的時間點上,只有相對較少的線程處於可執行的狀態(另外的線程可能因爲各類緣由處於阻塞狀態),在這種狀況下,沒有什麼理由須要去管理線程的優先級。另外一些狀況下,在線程優先級上的調整可能會對併發系統的調優起到一些做用。
範圍 用途 10 Crisis management(應急處理) 7-9 Interactive, event-driven(交互相關,事件驅動) 4-6 IO-bound(IO限制類) 2-3 Background computation(後臺計算) 1 Run only if nothing else can(僅在沒有任何線程運行時運行的)
只有不多幾個方法能夠用於跨線程交流:
每一個線程都有一個相關的Boolean類型的中斷標識。在線程t上調用t.interrupt會將該線程的中斷標識設爲true,除非線程t正處於Object.wait,Thread.sleep,或者Thread.join,這些狀況下interrupt調用會致使t上的這些操做拋出InterruptedException異常,可是t的中斷標識會被設爲false。 任何一個線程的中斷狀態均可以經過調用isInterrupted方法來獲得。若是線程已經經過interrupt方法被中斷,這個方法將會返回true。 可是若是調用了Thread.interrupted方法且中斷標識尚未被重置,或者是線程處於wait,sleep,join過程當中,調用isInterrupted方法將會拋出InterruptedException異常。調用t.join()方法將會暫停執行調用線程,直到線程t執行完畢:當t.isAlive()方法返回false的時候調用t.join()將會直接返回(return)。另外一個帶參數毫秒(millisecond)的join方法在被調用時,若是線程沒可以在指定的時間內完成,調用線程將從新獲得控制權。由於isAlive方法的實現原理,因此在一個尚未啓動的線程上調用join方法是沒有任何意義的。一樣的,試圖在一個尚未建立的線程上調用join方法也是不明智的。 起初,Thread類還支持一些另一些控制方法:suspend,resume,stop以及destroy。這幾個方法已經被聲明過時。其中destroy方法歷來沒有被實現,估計之後也不會。而經過使用等待/喚醒機制增長suspend和resume方法在安全性和可靠性的效果有所欠缺,將在3.2章節進行具體討論。而stop方法所帶來的問題也將在3.1.2.3進行探討。
Thread類中的部分方法被設計爲只適用於當前正在運行的線程(即調用Thread方法的線程)。爲強調這點,這些方法都被聲明爲靜態的。
Thread.currentThread方法會返回當前線程的引用,獲得這個引用能夠用來調用其餘的非靜態方法,好比Thread.currentThread().getPriority()會返回調用線程的優先級。 Thread.interrupted方法會清除當前線程的中斷狀態並返回前一個狀態。(一個線程的中斷狀態是不容許被其餘線程清除的) Thread.sleep(long msecs)方法會使得當前線程暫停執行至少msecs毫秒。 Thread.yield方法純粹只是建議Java虛擬機對其餘已經處於就緒狀態的線程(若是有的話)調度執行,而不是當前線程。最終Java虛擬機如何去實現這種行爲就徹底看其喜愛了。
儘管缺少保障,但在不支持分時間片/可搶佔式的線程調度方式的單CPU的Java虛擬機實現上,yield方法依然可以起到切實的做用。在這種狀況下,線程只在被阻塞的狀況下(好比等待IO,或是調用了sleep等)纔會進行從新調度。在這些系統上,那些執行非阻塞的耗時的計算任務的線程就會佔用CPU很長的時間,最終致使應用的響應能力下降。若是一個非阻塞的耗時計算線程會致使時間處理線程或者其餘交互線程超出可容忍的限度的話,就能夠在其中插入yield操做(或者是sleep),使得具備較低線程優先級的線程也能夠執行。爲了不沒必要要的影響,你能夠只在偶然間調用yield方法,好比,能夠在一個循環中插入以下代碼:if (Math.random() < 0.01) Thread.yield();
在支持可搶佔式調度的Java虛擬機實現上,線程調度器忽略yield操做多是最完美的策略,特別是在多核處理器上。
每個線程都是一個線程組中的成員。默認狀況下,新建線程和建立它的線程屬於同一個線程組。線程組是以樹狀分佈的。當建立一個新的線程組,這個線程組成爲當前線程組的子組。getThreadGroup方法會返回當前線程所屬的線程組,對應地,ThreadGroup類也有方法能夠獲得哪些線程目前屬於這個線程組,好比enumerate方法。
ThreadGroup類存在的一個目的是支持安全策略來動態的限制對該組的線程操做。好比對不屬於同一組的線程調用interrupt是不合法的。這是爲避免某些問題(好比,一個applet線程嘗試殺掉主屏幕的刷新線程)所採起的措施。ThreadGroup也能夠爲該組全部線程設置一個最大的線程優先級。
線程組每每不會直接在程序中被使用。在大多數的應用中,若是僅僅是爲在程序中跟蹤線程對象的分組,那麼普通的集合類(好比java.util.Vector)應是更好的選擇。
在ThreadGroup類爲數很少的幾個方法中,uncaughtException方法倒是很是有用的,當線程組中的某個線程因拋出未檢測的異常(好比空指針異常NullPointerException)而中斷的時候,調用這個方法能夠打印出線程的調用棧信息。
每個Object類及其子類的實例都擁有一個鎖。其中,標量類型int,float等不是對象類型,可是標量類型能夠經過其包裝類來做爲鎖。單獨的成員變量是不能被標明爲同步的。鎖只能用在使用了這些變量的方法上。然而正如在2.2.7.4上描述的,成員變量能夠被聲明爲volatile,這種方式會影響該變量的原子性,可見性以及排序性。
相似的,持有標量變量元素的數組對象擁有鎖,可是其中的標量元素卻不擁有鎖。(也就是說,沒有辦法將數組成員聲明爲volatile類型的)。若是鎖住了一個數組並不表明其數組成員均可以被原子的鎖定。也沒有能在一個原子操做中鎖住多個對象的方法。
Class實例本質上是個對象。正以下所述,在靜態同步方法中用的就是類對象的鎖。
使用synchronized關鍵字,有兩種語法結構:同步代碼塊和同步方法。同步代碼塊須要提供一個做爲鎖的對象參數。這就容許了任意方法能夠去鎖任一一個對象。但在同步代碼塊中使用的最普通的參數倒是this。
同步代碼塊被認爲比同步方法更加的基礎。以下兩種聲明方式是等同的:
1 synchronized void f() { /* body / } 2 void f() { synchronized(this) { / body */ } } synchronized關鍵字並非方法簽名的一部分。因此當子類覆寫父類中的同步方法或是接口中聲明的同步方法的時候,synchronized修飾符是不會被自動繼承的,另外,構造方法不多是真正同步的(儘管能夠在構造方法中使用同步塊)。
同步實例方法在其子類和父類中使用一樣的鎖。可是內部類方法的同步卻獨立於其外部類, 然而一個非靜態的內部類方法能夠經過下面這種方式鎖住其外部類:
1 synchronized(OuterClass.this) { /* body */ }
使用synchronized關鍵字須遵循一套內置的鎖等待-釋放機制。全部的鎖都是塊結構的。當進入一個同步方法或同步塊的時候必須得到該鎖,而退出的時候(即便是異常退出)必須釋放這個鎖。你不能忘記釋放鎖。
鎖操做是創建在獨立的線程上的而不是獨立的調用基礎上。一個線程可以進入一個同步代碼的條件是當前鎖未被佔用或者是當前線程已經佔用了這個鎖,不然線程就會阻塞住。(這種可重入鎖或是遞歸鎖不一樣於POSIX線程)。這就容許一個同步方法能夠去直接調用同一個鎖管理的另外一個同步方法,而不須要被凍結(注:即不須要再經歷釋放鎖-阻塞-申請鎖的過程)。
同步方法或同步塊遵循這種鎖獲取/鎖釋放的機制有一個前提,那就是全部的同步方法或同步塊都是在同一個鎖對象上。若是一個同步方法正在執行中,其餘的非同步方法也能夠在任什麼時候候執行。也就是說,同步不等於原子性,可是同步機制能夠用來實現原子性。
當一個線程釋放鎖的時候,另外一個線程可能正等待這個鎖(也多是同一個線程,由於這個線程可能須要進入另外一個同步方法)。可是關於哪個線程可以緊接着得到這個鎖以及何時,這是沒有任何保證的。(也就是,沒有任何的公平性保證-見3.4.1.5)另外,沒有什麼辦法可以獲得一個給定的鎖正被哪一個線程擁有着。
正如2.2.7討論的,除了鎖控制以外,同步也會對底層的內存系統帶來反作用。
鎖住一個對象並不會原子性的保護該對象類或其父類的靜態成員變量。而應該經過同步的靜態方法或代碼塊來保證訪問一個靜態的成員變量。靜態同步使用的是靜態方法鎖聲明的類對象所擁有的鎖。類C的靜態鎖能夠經過內置的實例方法獲取到: synchronized(C.class) { /* body */ }
每一個類所對應的靜態鎖和其餘的類(包括其父類)沒有任何的關係。經過在子類中增長一個靜態同步方法來試圖保護父類中的靜態成員變量是無效的。應使用顯式的代碼塊來代替。
以下這種方式也是一種很差的實踐: synchronized(getClass()) { /* body */ } // Do not use 這種方式,可能鎖住的實際中的類,並非須要保護的靜態成員變量所對應的類(有多是其子類)
Java虛擬機在類加載和類初始化階段,內部得到並釋放類鎖。除非你要去寫一個特殊的類加載器或者須要使用多個鎖來控制靜態初始順序,這些內部機制不該該干擾普通類對象的同步方法和同步塊的使用。Java虛擬機沒有什麼內部操做能夠獨立的獲取你建立和使用的類對象的鎖。然而當你繼承java.*的類的時候,你須要特別當心這些類中使用的鎖機制。
正如每一個對象都有一個鎖同樣,每個對象同時擁有一個由這些方法(wait,notify,notifyAll,Thread,interrupt)管理的一個等待集合。擁有鎖和等待集合的實體一般被稱爲監視器(雖然每種語言定義的細節略有不一樣),任何一個對象均可以做爲一個監視器。
對象的等待集合是由Java虛擬機來管理的。每一個等待集合上都持有在當前對象上等待但還沒有被喚醒或是釋放的阻塞線程。
由於與等待集合交互的方法(wait,notify,notifyAll)只在擁有目標對象的鎖的狀況下才被調用,所以沒法在編譯階段驗證其正確性,但在運行階段錯誤的操做會致使拋出IllegalMonitorStateException異常。
這些方法的操做描述以下:
調用wait方法會產生以下操做:
若是當前線程已經終止,那麼這個方法會當即退出並拋出一個InterruptedException異常。不然當前線程就進入阻塞狀態。 Java虛擬機將該線程放置在目標對象的等待集合中。 釋放目標對象的同步鎖,可是除此以外的其餘鎖依然由該線程持有。即便是在目標對象上屢次嵌套的同步調用,所持有的可重入鎖也會完整的釋放。這樣,後面恢復的時候,當前的鎖狀態可以徹底地恢復。
調用Notify會產生以下操做:
Java虛擬機從目標對象的等待集合中隨意選擇一個線程(稱爲T,前提是等待集合中還存在一個或多個線程)並從等待集合中移出T。當等待集合中存在多個線程時,並無機制保證哪一個線程會被選擇到。 線程T必須從新得到目標對象的鎖,直到有線程調用notify釋放該鎖,不然線程會一直阻塞下去。若是其餘線程先一步得到了該鎖,那麼線程T將繼續進入阻塞狀態。 線程T從以前wait的點開始繼續執行。
notifyAll方法與notify方法的運行機制是同樣的,只是這些過程是在對象等待集合中的全部線程上發生(事實上,是同時發生)的。可是由於這些線程都須要得到同一個鎖,最終也只能有一個線程繼續執行下去。
若是在一個因wait而中斷的線程上調用Thread.interrupt方法,以後的處理機制和notify機制相同,只是在從新獲取這個鎖以後,該方法將會拋出一個InterruptedException異常而且線程的中斷標識將被設爲false。若是interrupt操做和一個notify操做在同一時間發生,那麼不能保證那個操做先被執行,所以任何一個結果都是可能的。(JLS的將來版本可能會對這些操做結果提供肯定性保證)
定時版本的wait方法,wait(long mesecs)和wait(long msecs,int nanosecs),參數指定了須要在等待集合中等待的最大時間值。若是在時間限制以內沒有被喚醒,它將自動釋放,除此以外,其餘的操做都和無參數的wait方法同樣。並無狀態可以代表線程正常喚醒與超時喚醒之間的不一樣。須要注意的是,wait(0)與wait(0,0)方法其實都具備特殊的意義,其至關於不限時的wait()方法,這可能與你的直覺相反。
因爲線程競爭,調度策略以及定時器粒度等方面的緣由,定時等待方法可能會消耗任意的時間。(注:關於定時器粒度並無任何的保證,目前大多數的Java虛擬機實現當參數設置小於1毫秒的時候,觀察的結果基本上在1~20毫秒之間)
Thread.sleep(long msecs)方法使用了定時等待的wait方法,可是使用的並非當前對象的同步鎖。它的效果以下描述: if (msecs != 0) { Object s = new Object(); synchronized(s) { s.wait(msecs); } } 固然,系統不須要使用這種方式去實現sleep方法。須要注意的,sleep(0)方法的含義是中斷線程至少零時間,隨便怎麼解釋都行。(譯者注:該方法有着特殊的做用,從原理上它能夠促使系統從新進行一次CPU競爭)。