閱讀276 返回首頁    go 阿裏雲 go 技術社區[雲棲]


Java並發結構

內容

  • 線程
  • 同步
  • 監視器

線程

線程是一個獨立執行的調用序列,同一個進程的線程在同一時刻共享一些係統資源(比如文件句柄等)也能訪問同一個進程所創建的對象資源(內存資源)。java.lang.Thread對象負責統計和控製這種行為。

每個程序都至少擁有一個線程-即作為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方法。新線程不會持有調用線程的任何同步鎖。

當一個線程正常地運行結束或者拋出某種未檢測的異常(比如,運行時異常(RuntimeException),錯誤(ERROR) 或者其子類)線程就會終止。當線程終止之後,是不能被重新啟動的。在同一個Thread上調用多次start方法會拋出 InvalidThreadStateException異常。

如果線程已經啟動但是還沒有終止,那麼調用isAlive方法就會返回true.即使線程由於某些原因處於阻塞(Blocked)狀態該方法依然返 回true。如果線程已經被取消(cancelled),那麼調用其isAlive在什麼時候返回false就因各Java虛擬機的實現而異了。沒有方法 可以得知一個處於非活動狀態的線程是否已經被啟動過了(譯者注:即線程在開始運行前和結束運行後都會返回false,你無法得知處於false的線程具體 的狀態)。另一點,雖然一個線程能夠得知同一個線程組的其他線程的標識,但是卻無法得知自己是由哪個線程調用啟動的。

優先級

Java虛擬機為了實現跨平台(不同的硬件平台和各種操作係統)的特性,Java語言在線程調度與調度公平性上未作出任何的承諾,甚至都不會嚴格保證線程會被執行。但是Java線程卻支持優先級的方法,這些方法會影響線程的調度:

每個線程都有一個優先級,分布在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。

同步代碼塊被認為比同步方法更加的基礎。如下兩種聲明方式是等同的:


synchronized void f() { /* body */ }
void f() { synchronized(this) { /* body */ } }

synchronized關鍵字並不是方法簽名的一部分。所以當子類覆寫父類中的同步方法或是接口中聲明的同步方法的時候,synchronized修飾符是不會被自動繼承的,另外,構造方法不可能是真正同步的(盡管可以在構造方法中使用同步塊)。

同步實例方法在其子類和父類中使用同樣的鎖。但是內部類方法的同步卻獨立於其外部類, 然而一個非靜態的內部類方法可以通過下麵這種方式鎖住其外部類:


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
調用wait方法會產生如下操作:

  • 如果當前線程已經終止,那麼這個方法會立即退出並拋出一個InterruptedException異常。否則當前線程就進入阻塞狀態。
  • Java虛擬機將該線程放置在目標對象的等待集合中。
  • 釋放目標對象的同步鎖,但是除此之外的其他鎖依然由該線程持有。即使是在目標對象上多次嵌套的同步調用,所持有的可重入鎖也會完整的釋放。這樣,後麵恢複的時候,當前的鎖狀態能夠完全地恢複。

Notify
調用Notify會產生如下操作:

  • Java虛擬機從目標對象的等待集合中隨意選擇一個線程(稱為T,前提是等待集合中還存在一個或多個線程)並從等待集合中移出T。當等待集合中存在多個線程時,並沒有機製保證哪個線程會被選擇到。
  • 線程T必須重新獲得目標對象的鎖,直到有線程調用notify釋放該鎖,否則線程會一直阻塞下去。如果其他線程先一步獲得了該鎖,那麼線程T將繼續進入阻塞狀態。
  • 線程T從之前wait的點開始繼續執行。

NotifyAll

notifyAll方法與notify方法的運行機製是一樣的,隻是這些過程是在對象等待集合中的所有線程上發生(事實上,是同時發生)的。但是因為這些線程都需要獲得同一個鎖,最終也隻能有一個線程繼續執行下去。

Interrupt(中斷)
如果在一個因wait而中斷的線程上調用Thread.interrupt方法,之後的處理機製和notify機製相同,隻是在重新獲取這個鎖之後,該方 法將會拋出一個InterruptedException異常並且線程的中斷標識將被設為false。如果interrupt操作和一個notify操作 在同一時間發生,那麼不能保證那個操作先被執行,因此任何一個結果都是可能的。(JLS的未來版本可能會對這些操作結果提供確定性保證)

Timed Wait(定時等待)
定時版本的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競爭)。

原文

Threads

A thread is a call sequence that executes independently of others, while at the same time possibly sharing underlying system resources such as files, as well as accessing other objects constructed within the same program (see �1.2.2). A java.lang.Thread object maintains bookkeeping and control for this activity.

Every program consists of at least one thread – the one that runs the main method of the class provided as a startup argument to the Java virtual machine (“JVM”). Other internal background threads may also be started during JVM initialization. The number and nature of such threads vary across JVM implementations. However, all user-level threads are explicitly constructed and started from the main thread, or from any other threads that they in turn create.

Here is a summary of the principal methods and properties of class Thread, as well as a few usage notes. They are further discussed and illustrated throughout this book. The JavaTM Language Specification (“JLS”) and the published API documentation should be consulted for more detailed and authoritative descriptions.

Construction

Different Thread constructors accept combinations of arguments supplying:

  • A Runnable object, in which case a subsequent Thread.start invokes run of the supplied Runnable object. If no Runnable is supplied, the default implementation of Thread.run returns immediately.
  • A String that serves as an identifier for the Thread. This can be useful for tracing and debugging, but plays no other role.
  • The ThreadGroup in which the new Thread should be placed. If access to the ThreadGroup is not allowed, a SecurityException is thrown.

Class Thread itself implements Runnable. So, rather than supplying the code to be run in a Runnable and using it as an argument to a Thread constructor, you can create a subclass of Thread that overrides the run method to perform the desired actions. However, the best default strategy is to define a Runnable as a separate class and supply it in a Thread constructor. Isolating code within a distinct class relieves you of worrying about any potential interactions of synchronized methods or blocks used in the Runnable with any that may be used by methods of class Thread. More generally, this separation allows independent control over the nature of the action and the context in which it is run: The same Runnable can be supplied to threads that are otherwise initialized in different ways, as well as to other lightweight executors (see �4.1.4). Also note that subclassing Thread precludes a class from subclassing any other class.

Thread objects also possess a daemon status attribute that cannot be set via any Thread constructor, but may be set only before a Thread is started. The method setDaemon asserts that the JVM may exit, abruptly terminating the thread, so long as all other non-daemon threads in the program have terminated. The isDaemon method returns status. The utility of daemon status is very limited. Even background threads often need to do some cleanup upon program exit. (The spelling of daemon, often pronounced as “day-mon”, is a relic of systems programming tradition. System daemons are continuous processes, for example print-queue managers, that are “always” present on a system.)

Starting threads

Invoking its start method causes an instance of class Thread to initiate its run method as an independent activity. None of the synchronization locks held by the caller thread are held by the new thread (see �2.2.1).

A Thread terminates when its run method completes by either returning normally or throwing an unchecked exception (i.e., RuntimeException, Error, or one of their subclasses). Threads are not restartable, even after they terminate. Invoking start more than once results in an InvalidThreadStateException.

The method isAlive returns true if a thread has been started but has not terminated. It will return true if the thread is merely blocked in some way. JVM implementations have been known to differ in the exact point at which isAlive returns false for threads that have been cancelled (see �3.1.2). There is no method that tells you whether a thread that is not isAlive has ever been started. Also, one thread cannot readily determine which other thread started it, although it may determine the identities of other threads in its ThreadGroup (see �1.1.2.6).

Priorities

To make it possible to implement the Java virtual machine across diverse hardware platforms and operating systems, the Java programming language makes no promises about scheduling or fairness, and does not even strictly guarantee that threads make forward progress (see �3.4.1.5). But threads do support priority methods that heuristically influence schedulers:

  • Each Thread has a priority, ranging between Thread.MIN_PRIORITY and Thread.MAX_PRIORITY (defined as 1 and 10 respectively).
  • By default, each new thread has the same priority as the thread that created it. The initial thread associated with a main by default has priority Thread.NORM_PRIORITY (5).
  • The current priority of any thread can be accessed via method getPriority.
  • The priority of any thread can be dynamically changed via method setPriority. The maximum allowed priority for a thread is bounded by its ThreadGroup.

When there are more runnable (see �1.3.2) threads than available CPUs, a scheduler is generally biased to prefer running those with higher priorities. The exact policy may and does vary across platforms. For example, some JVM implementations always select the thread with the highest current priority (with ties broken arbitrarily). Some JVM implementations map the ten Thread priorities into a smaller number of system-supported categories, so threads with different priorities may be treated equally. And some mix declared priorities with aging schemes or other scheduling policies to ensure that even low-priority threads eventually get a chance to run. Also, setting priorities may, but need not, affect scheduling with respect to other programs running on the same computer system.

Priorities have no other bearing on semantics or correctness (see �1.3). In particular, priority manipulations cannot be used as a substitute for locking. Priorities can be used only to express the relative importance or urgency of different threads, where these priority indications would be useful to take into account when there is heavy contention among threads trying to get a chance to execute. For example, setting the priorities of the particle animation threads in ParticleApplet below that of the applet thread constructing them might on some systems improve responsiveness to mouse clicks, and would at least not hurt responsiveness on others. But programs should be designed to run correctly (although perhaps not as responsively) even if setPriority is defined as a no-op. (Similar remarks hold for yield; see �1.1.2.5.)

The following table gives one set of general conventions for linking task categories to priority settings. In many concurrent applications, relatively few threads are actually runnable at any given time (others are all blocked in some way), in which case there is little reason to manipulate priorities. In other cases, minor tweaks in priority settings may play a small part in the final tuning of a concurrent system.


Range	Use
10	Crisis management
7-9	Interactive, event-driven
4-6	IO-bound
2-3	Background computation
1	Run only if nothing else can


Control methods

Only a few methods are available for communicating across threads:

  • Each Thread has an associated boolean interruption status (see �3.1.2). Invoking t.interrupt for some Thread t sets t’s interruption status to true, unless Thread t is engaged in Object.wait, Thread.sleep, or Thread.join; in this case interrupt causes these actions (in t) to throw InterruptedException, but t’s interruption status is set to false.
  • The interruption status of any Thread can be inspected using method isInterrupted. This method returns true if the thread has been interrupted via the interrupt method but the status has not since been reset either by the thread invoking Thread.interrupted (see �1.1.2.5) or in the course of wait, sleep, or join throwing InterruptedException.
  • Invoking t.join() for Thread t suspends the caller until the target Thread t completes: the call to t.join() returns when t.isAlive() is false (see �4.3.2). A version with a (millisecond) time argument returns control even if the thread has not completed within the specified time limit. Because of how isAlive is defined, it makes no sense to invoke join on a thread that has not been started. For similar reasons, it is unwise to try to join a Thread that you did not create.

Originally, class Thread supported the additional control methods suspend, resume, stop, and destroy. Methods suspend, resume, and stop have since been deprecated; method destroy has never been implemented in any release and probably never will be. The effects of methods suspend and resume can be obtained more safely and reliably using the waiting and notification techniques discussed in �3.2. The problems surrounding stop are discussed in �3.1.2.3.

Static methods

Some Thread class methods are designed to be applied only to the thread that is currently running (i.e., the thread making the call to the Thread method). To enforce this, these methods are declared as static.

  • Thread.currentThread returns a reference to the current Thread. This reference may then be used to invoke other (non-static) methods. For example, Thread.currentThread().getPriority() returns the priority of the thread making the call.
  • Thread.interrupted clears interruption status of the current Thread and returns previous status. (Thus, one Thread’s interruption status cannot be cleared from other threads.)
  • Thread.sleep(long msecs) causes the current thread to suspend for at least msecs milliseconds (see �3.2.2).

Thread.yield is a purely heuristic hint advising the JVM that if there are any other runnable but non-running threads, the scheduler should run one or more of these threads rather than the current thread. The JVM may interpret this hint in any way it likes.

Despite the lack of guarantees, yield can be pragmatically effective on some single-CPU JVM implementations that do not use time-sliced pre-emptive scheduling (see �1.2.2). In this case, threads are rescheduled only when one blocks (for example on IO, or via sleep). On these systems, threads that perform time-consuming non-blocking computations can tie up a CPU for extended periods, decreasing the responsiveness of an application. As a safeguard, methods performing non-blocking computations that might exceed acceptable response times for event handlers or other reactive threads can insert yields (or perhaps even sleeps) and, when desirable, also run at lower priority settings. To minimize unnecessary impact, you can arrange to invoke yield only occasionally; for example, a loop might contain:
if (Math.random() < 0.01) Thread.yield();

On JVM implementations that employ pre-emptive scheduling policies, especially those on multiprocessors, it is possible and even desirable that the scheduler will simply ignore this hint provided by yield.

ThreadGroups

Every Thread is constructed as a member of a ThreadGroup, by default the same group as that of the Thread issuing the constructor for it. ThreadGroups nest in a tree-like fashion. When an object constructs a new ThreadGroup, it is nested under its current group. The method getThreadGroup returns the group of any thread. The ThreadGroup class in turn supports methods such as enumerate that indicate which threads are currently in the group.

One purpose of class ThreadGroup is to support security policies that dynamically restrict access to Thread operations; for example, to make it illegal to interrupt a thread that is not in your group. This is one part of a set of protective measures against problems that could occur, for example, if an applet were to try to kill the main screen display update thread. ThreadGroups may also place a ceiling on the maximum priority that any member thread can possess.

ThreadGroups tend not to be used directly in thread-based programs. In most applications, normal collection classes (for example java.util.Vector) are better choices for tracking groups of Thread objects for application-dependent purposes.

Among the few ThreadGroup methods that commonly come into play in concurrent programs is method uncaughtException, which is invoked when a thread in a group terminates due to an uncaught unchecked exception (for example a NullPointerException). This method normally causes a stack trace to be printed.


Synchronization

Objects and locks

Every instance of class Object and its subclasses possesses a lock. Scalars of type int, float, etc., are not Objects. Scalar fields can be locked only via their enclosing objects. Individual fields cannot be marked as synchronized. Locking may be applied only to the use of fields within methods. However, as described in �2.2.7.4, fields can be declared as volatile, which affects atomicity, visibility, and ordering properties surrounding their use.

Similarly, array objects holding scalar elements possess locks, but their individual scalar elements do not. (Further, there is no way to declare array elements as volatile.) Locking an array of Objects does not automatically lock all its elements. There are no constructs for simultaneously locking multiple objects in a single atomic operation.

Class instances are Objects. As described below, the locks associated with Class objects are used in static synchronized methods.

Synchronized methods and blocks

There are two syntactic forms based on the synchronized keyword, blocks and methods. Block synchronization takes an argument of which object to lock. This allows any method to lock any object. The most common argument to synchronized blocks is this.

Block synchronization is considered more fundamental than method synchronization. A declaration:
synchronized void f() { /* body */ }
is equivalent to:
void f() { synchronized(this) { /* body */ } }

The synchronized keyword is not considered to be part of a method’s signature. So the synchronized modifier is not automatically inherited when subclasses override superclass methods, and methods in interfaces cannot be declared as synchronized. Also, constructors cannot be qualified as synchronized (although block synchronization can be used within constructors).

Synchronized instance methods in subclasses employ the same lock as those in their superclasses. But synchronization in an inner class method is independent of its outer class. However, a non-static inner class method can lock its containing class, say OuterClass, via code blocks using:
synchronized(OuterClass.this) { /* body */ }.

Acquiring and releasing locks

Locking obeys a built-in acquire-release protocol controlled only by use of the synchronized keyword. All locking is block-structured. A lock is acquired on entry to a synchronized method or block, and released on exit, even if the exit occurs due to an exception. You cannot forget to release a lock.

Locks operate on a per-thread, not per-invocation basis. A thread hitting synchronized passes if the lock is free or the thread already possess the lock, and otherwise blocks. (This reentrant or recursive locking differs from the default policy used for example in POSIX threads.) Among other effects, this allows one synchronized method to make a self-call to another synchronized method on the same object without freezing up.

A synchronized method or block obeys the acquire-release protocol only with respect to other synchronized methods and blocks on the same target object. Methods that are not synchronized may still execute at any time, even if a synchronized method is in progress. In other words, synchronized is not equivalent to atomic, but synchronization can be used to achieve atomicity.

When one thread releases a lock, another thread may acquire it (perhaps the same thread, if it hits another synchronized method). But there is no guarantee about which of any blocked threads will acquire the lock next or when they will do so. (In particular, there are no fairness guarantees – see �3.4.1.5.) There is no mechanism to discover whether a given lock is being held by some thread.

As discussed in �2.2.7, in addition to controlling locking, synchronized also has the side-effect of synchronizing the underlying memory system.

Statics

Locking an object does not automatically protect access to static fields of that object’s class or any of its superclasses. Access to static fields is instead protected via synchronized static methods and blocks. Static synchronization employs the lock possessed by the Class object associated with the class the static methods are declared in. The static lock for class C can also be accessed inside instance methods via:
synchronized(C.class) { /* body */ }

The static lock associated with each class is unrelated to that of any other class, including its superclasses. It is not effective to add a new static synchronized method in a subclass that attempts to protect static fields declared in a superclass. Use the explicit block version instead.

It is also poor practice to use constructions of the form:
synchronized(getClass()) { /* body */ } // Do not use

This locks the actual class, which might be different from (a subclass of) the class defining the static fields that need protecting.

The JVM internally obtains and releases the locks for Class objects during class loading and initialization. Unless you are writing a special ClassLoader or holding multiple locks during static initialization sequences, these internal mechanics cannot interfere with the use of ordinary methods and blocks synchronized on Class objects. No other internal JVM actions independently acquire any locks for any objects of classes that you create and use. However, if you subclass java.* classes, you should be aware of the locking policies used in these classes.


Monitors

In the same way that every Object has a lock (see �2.2.1), every Object has a wait set that is manipulated only by methods wait, notify, notifyAll, and Thread.interrupt. Entities possessing both locks and wait sets are generally called monitors (although almost every language defines details somewhat differently). Any Object can serve as a monitor.

The wait set for each object is maintained internally by the JVM. Each set holds threads blocked by wait on the object until corresponding notifications are invoked or the waits are otherwise released.

Because of the way in which wait sets interact with locks, the methods wait, notify, and notifyAll may be invoked only when the synchronization lock is held on their targets. Compliance generally cannot be verified at compile time. Failure to comply causes these operations to throw an IllegalMonitorStateException at run time.

The actions of these methods are as follows:

Wait
A wait invocation results in the following actions:
  • If the current thread has been interrupted, then the method exits immediately, throwing an InterruptedException. Otherwise, the current thread is blocked.
  • The JVM places the thread in the internal and otherwise inaccessible wait set associated with the target object.
  • The synchronization lock for the target object is released, but all other locks held by the thread are retained. A full release is obtained even if the lock is re-entrantly held due to nested synchronized calls on the target object. Upon later resumption, the lock status is fully restored.
Notify
A notify invocation results in the following actions:
  • If one exists, an arbitrarily chosen thread, say T, is removed by the JVM from the internal wait set associated with the target object. There is no guarantee about which waiting thread will be selected when the wait set contains more than one thread – see �3.4.1.5.
  • T must re-obtain the synchronization lock for the target object, which will always cause it to block at least until the thread calling notify releases the lock. It will continue to block if some other thread obtains the lock first.
  • T is then resumed from the point of its wait.
NotifyAll
A notifyAll works in the same way as notify except that the steps occur (in effect, simultaneously) for all threads in the wait set for the object. However, because they must acquire the lock, threads continue one at a time. 
Interrupt
If Thread.interrupt is invoked for a thread suspended in a wait, the same notify mechanics apply, except that after re-acquiring the lock, the method throws an InterruptedException and the thread’s interruption status is set to false. If an interrupt and a notify occur at about the same time, there is no guarantee about which action has precedence, so either result is possible. (Future revisions of JLS may introduce deterministic guarantees about these outcomes.) 
Timed Waits
The timed versions of the wait method, wait(long msecs) and wait(long msecs, int nanosecs), take arguments specifying the desired maximum time to remain in the wait set. They operate in the same way as the untimed version except that if a wait has not been notified before its time bound, it is released automatically. There is no status indication differentiating waits that return via notifications versus time-outs. Counterintuitively, wait(0) and wait(0, 0) both have the special meaning of being equivalent to an ordinary untimed wait().A timed wait may resume an arbitrary amount of time after the requested bound due to thread contention, scheduling policies, and timer granularities. (There is no guarantee about granularity. Most JVM implementations have observed response times in the 1-20ms range for arguments less than 1ms.)The Thread.sleep(long msecs) method uses a timed wait, but does not tie up the current object’s synchronization lock. It acts as if it were defined as:
   
 if (msecs != 0)  {
      Object s = new Object(); 
      synchronized(s) { s.wait(msecs); }
    }

Of course, a system need not implement sleep in exactly this way. Note that sleep(0) pauses for at least no time, whatever that means.


文章轉自 並發編程網-ifeve.com



最後更新:2017-05-23 11:02:58

  上一篇:go  volatile是否能保證數組中元素的可見性?
  下一篇:go  Java IO: 字符流的Piped和CharArray