Java 與線程 深入理解Java虛擬機 總結

線程的實現

????????我們知道赊舶,線程是比進程更輕量級的調度執(zhí)行單位疚沐,線程的引入可以把一個進程的資源分配和執(zhí)行調度分開鹃骂,各個線程既可以共享進程資源(內存地址、文件 I/O 等)存筏,又可以獨立調度(線程是 CPU 調度的基本單位)娜庇。

??????? 主流的操作系統都提供了線程實現,Java 語言則提供了在不同硬件和操作系統平臺下對線程操作的統一處理方篮,每個已經執(zhí)行 start() 且還未結束的 java.lang.Thread 類的實例就代表了一個線程名秀。我們注意到 Thread 類與大部分的 Java API 有顯著的差別,它的所有關鍵方法都是聲明為 Native 的藕溅。在 Java API 中匕得,一個 Native 方法往往意味著這個方法沒有使用或無法使用平臺無關的手段來實現(當然也可能是為了執(zhí)行效率而使用 Native 方法,不過,通常最高效率的手段也就是平臺相關的手段)汁掠。正因為如此略吨,作者把本節(jié)的標題定為 “線程的實現” 而不是 “Java 線程的實現”。

??????? 實現線程主要有 3 種方式:使用內核線程實現考阱、使用用戶線程實現和使用用戶線程加輕量級進程混合實現翠忠。

1. 使用內核線程實現

??????? 內核線程(Kernel-Level Thread,KLT)就是直接由操作系統內核(Kernel乞榨,下稱內核)支持的線程秽之,這種線程由內核來完成線程切換,內核通過操縱調度器(Scheduler)對線程進行調度吃既,并負責將線程的任務映射到各個處理器上考榨。每個內核線程可以視為內核的一個分身,這樣操作系統就有能力同時處理多件事情鹦倚,支持多線程的內核就叫做多線程內核(Multi-Threads Kernel)河质。

?????????程序一般不會直接去使用內核線程,而是去使用內核線程的一種高級接口——輕量級進程(Light Weight Process震叙,LWP)掀鹅,輕量級進程就是我們通常意義上所講的線程,由于每個輕量級進程都由一個內核線程支持媒楼,因此只有先支持內核線程淫半,才能有輕量級進程。這種輕量級進程與內核線程之間 1 : 1 的關系稱為一對一的線程模型匣砖,如圖 12-3 所示科吭。

????????由于內核線程的支持,每個輕量級進程都會成為一個獨立的調度單元猴鲫,即使有一個輕量級進程在系統調用中阻塞了对人,也不會影響整個進程繼續(xù)工作,但是輕量級進程具有它的局限性:首先拂共,由于是基于內核線程實現的牺弄,所以各種線程操作,如創(chuàng)建宜狐、析構及同步势告,都需要進行系統調用。而系統調用的代價相對較高抚恒,需要在用戶態(tài)(User Mode)和內核態(tài)(Kernel Mode)中來回切換咱台。其次,每個輕量級進程都需要有一個內核線程的支持俭驮,因此輕量級進程要消耗一定的內核資源(如內核線程的椈啬纾空間)春贸,因此一個系統支持輕量級進程的數量是有限的

2. 使用用戶線程實現

??????? 從廣義上來講遗遵,一個線程只要不是內核線程萍恕,就可以認為是用戶線程(User Thread,UT)车要,因此允粤,從這個定義上講,輕量級進程也屬于用戶線程翼岁,但輕量級進程的實現始終是建立在內核之上的类垫,許多操作都要進行系統調用,效率會收到限制登澜。

????????而狹義上的用戶線程指的是完全建立在用戶控件的線程庫上阔挠,系統內核不能感知線程存在的實現飘庄。用戶線程的建立脑蠕、同步、銷毀和調度完全在用戶態(tài)中完成跪削,不需要內核的幫助谴仙。如果程序實現得當,這種線程不需要切換到內核態(tài)碾盐,因此操作可以是非郴味澹快速且低消耗的,也可以支持規(guī)模更大的線程數量毫玖,部分高性能數據庫中的多線程就是由用戶線程實現的掀虎。這種進程與用戶線程之間 1:N 的關系稱為一對多的線程模型,如圖 12-4 所示付枫。

????????使用用戶線程的優(yōu)勢在于不需要系統內核支援烹玉,劣勢也在于沒有系統內核的支援,所有的線程操作都需要用戶程序自己處理阐滩。線程的創(chuàng)建二打、切換和調度都是需要考慮的問題,而且由于操作系統只把處理器資源分配到進程掂榔,那諸如 “阻塞如何處理”继效、“多處理器系統中如何將線程映射到其他處理器上” 這類問題解決起來將會異常困難,甚至不可能完成装获。因而使用用戶線程實現的程序一般都比較復雜瑞信,除了以前在不支持多線程的操作系統中(如 DOS)的多線程程序與少數有特殊需求的程序外,現在使用用戶線程的程序越來越少了穴豫,Java喧伞、Ruby 等語言都曾經使用過用戶線程,最終又都放棄使用它。

3. 使用用戶線程加輕量級進程混合實現

????????線程除了依賴內核線程實現和完全由用戶程序自己實現之外潘鲫,還有一種將內核線程與用戶線程一起使用的實現方式翁逞。在這種混合實現下,既存在用戶線程溉仑,也存在輕量級進程挖函。用戶線程還是完全建立在用戶空間中,因此用戶線程的創(chuàng)建浊竟、切換怨喘、析構等操作依然廉價,并且可以支持大規(guī)模的用戶線程并發(fā)振定。而操作系統提供支持的輕量級進程則作為用戶線程和內核線程之間的橋梁必怜,這樣可以使用內核提供的線程調度功能及處理器映射,并且用戶線程的系統調用要通過輕量級線程來完成后频,大大降低了整個進程被完全阻塞的風險梳庆。在這種混合模式中,用戶線程與輕量級進程的數量比是不定的卑惜,即為 N:M 的關系膏执,如圖 12-5 所示,這種就是多對多的線程模型露久。

??????? 許多 UNIX 系列的操作系統更米,如 Solaris、HP-UX 等都提供了 N:M 的線程模型實現毫痕。

4. Java 線程的實現

????????Java 線程在 JDK 1.2 之前征峦,是基于稱為 “綠色線程”(Green Threads)的用戶線程實現的,而在 JDK 1.2 中消请,線程模型替換為基于操作系統原生線程模型來實現栏笆。因此,在目前的 JDK 版本中梯啤,操作系統支持怎樣的線程模型竖伯,在很大程度上決定了 Java 虛擬機的線程是怎樣映射的,這點在不同的平臺上沒有辦法達成一致因宇,虛擬機規(guī)范中也并未限定 Java 線程需要使用哪種線程模型來實現七婴。線程模型只對線程的并發(fā)規(guī)模和操作成本產生影響,對 Java 程序的編碼和運行過程來說察滑,這些差異都是透明的打厘。

??????? 對于 Sun SDK 來說,它的 Windows 版與 Linux 版都是使用一對一的線程模型實現的贺辰,一條 Java 線程就映射到一條輕量級進程之中户盯,因為 Windows 和 Linux 系統提供的線程模型就是一對一的嵌施。

??????? 而在 Solaris 平臺中,由于操作系統的線程特性可以同時支持一對一(通過 Bound Threads 或 Alternate Libthread 實現)級多對多(通過 LWP/Thread Based Synchronization 實現)的線程模型莽鸭,因此在 Solaris 版的 JDK 中也對應提供了兩個平臺專有的虛擬機參數:-XX:+UseLWPSynchronization(默認值)和 -XX:+UseBoundThreads 來明確指定虛擬機使用哪種線程模型吗伤。

Java線程調度

????????線程調度是指系統為線程分配處理器使用權的過程,主要調度方式有兩種硫眨,分別是協同式線程調度(Cooperative Threads-Scheduling)和搶占式線程調度(Preemptive Threads-Scheduling)足淆。

????????? 如果使用協同式調度的多線程系統,線程的執(zhí)行時間由線程本身來控制礁阁,線程把自己的工作執(zhí)行完了之后巧号,要主動通知系統切換到另外一個線程上。協同式多線程的最大好處是實現簡單姥闭,而且由于線程要把自己的事情干完后才會進行線程切換丹鸿,切換操作對線程自己是可知的,所以沒有什么線程同步的問題棚品。Lua 語言中的“協同例程”就是這類實現靠欢。它的壞處也很明顯:線程執(zhí)行時間不可控制,甚至如果一個線程編寫有問題南片,一直不告知系統進行線程切換掺涛,那么程序就會一直阻塞在那里庭敦。很久以前的 Windows 3.x 系統就是使用協同式來實現多進程多任務的疼进,相當不穩(wěn)定,一個進程堅持不讓出 CPU 執(zhí)行時間久可能會導致整個系統崩潰秧廉。

?????????如果使用搶占式調度的多線程系統伞广,那么每個線程將由系統來分配執(zhí)行時間,線程的切換不由線程本身來決定(在 Java 中疼电,Thread.yield() 可以讓出執(zhí)行時間嚼锄,但是要獲取執(zhí)行時間的話,線程本身是沒有什么辦法的)蔽豺。在這種實現線程調度的方式下区丑,線程的執(zhí)行時間是系統可控的,也不會有一個線程導致整個進程阻塞的問題修陡,Java?使用的線程調度方式就是搶占式調度沧侥。與前面所說的 Windows 3.x 的例子相對,在 Windows 9x/NT 內核中就是使用搶占式來實現多進程的魄鸦,當一個進程出了問題宴杀,我們還可以使用任務管理器把這個進程 “殺掉”,而不至于導致系統崩潰拾因。

??????? 雖然 Java 線程調度是系統自動完成的旺罢,但是我們還是可以 “建議” 系統給某些線程多分配一點執(zhí)行時間旷余,另外一些線程則可以少分配一點——這項操作可以通過設置線程優(yōu)先級來完成。Java 語言一共設置了 10 個級別的線程優(yōu)先級(Thread.MIN_PRORITY 至 Thread.MAX_PRIORITY)扁达,在兩個線程同時處于 Ready 狀態(tài)時正卧,優(yōu)先級越高的線程越容易被系統選擇執(zhí)行。

????????不過跪解,線程優(yōu)先級并不是太靠譜穗酥,原因是?Java 的線程是通過映射到系統的原生線程上來實現的,所以線程調度最終還是取決于操作系統惠遏,雖然現在很多操作系統都提供線程優(yōu)先級的概念砾跃,但是并不見得能與 Java 線程的優(yōu)先級一一對應,如 Solaris 中有 2147483648(2^31)種優(yōu)先級节吮,但Windows 中就只有 7 種抽高,比 Java 線程優(yōu)先級多的系統還好說,中間留下一點空位就可以了透绩,但比 Java 線程優(yōu)先級少的系統翘骂,就不得不出現幾個優(yōu)先級相同的情況了,表 12-1 顯示了 Java 線程優(yōu)先級與 Windows 線程優(yōu)先級之間的對應關系帚豪,Windows 平臺的 JDK 中使用了除 THREAD_PRIORITY_IDLE 之外的其余 6 種線程優(yōu)先級碳竟。

????????上文說到 “線程優(yōu)先級并不是太靠譜”,不僅僅是說在一些平臺上不同的優(yōu)先級實際會變得相同這一點狸臣,還有其他情況讓我們不能太讓依賴優(yōu)先級:優(yōu)先級可能會被系統自行改變莹桅。例如,在 Windows 系統中存在一個存在一個稱謂? “優(yōu)先級推進器” (Priority Boosting烛亦,當然它可以被關閉掉)的功能诈泼,它的大致作用就是當系統發(fā)現一個線程執(zhí)行得到特別 “勤奮努力” 的話,可能會越過線程優(yōu)先級去為它分配執(zhí)行時間煤禽。因此铐达,我們不能在程序中通過優(yōu)先級來完全準確地判斷一組狀態(tài)都為 Ready 的線程將會先執(zhí)行哪一個。

狀態(tài)轉換

????????Java 語言定義了 5 種線程狀態(tài)檬果,在任意一個時間點瓮孙,一個線程有且只有其中的一種狀態(tài),這 5 種狀態(tài)分別如下选脊。

新建(New):創(chuàng)建后尚未啟動的線程處于這種狀態(tài)杭抠。

運行(Runnable):Runnable 包括了操作系統線程狀態(tài)中的 Running 和 Ready,也就是處于此狀態(tài)的線程有可能正在執(zhí)行知牌,也有可能正在等待著 CPU 為它分配執(zhí)行時間祈争。

無限期等待(Waiting):處于這種狀態(tài)的線程不會被分配 CPU 執(zhí)行時間,它們要等待被其他線程顯式地喚醒角寸。以下方法會讓線程陷入無限期的等待狀態(tài):

沒有設置 Timeout 參數的 Object.wait() 方法菩混。

沒有設置 Timeout 參數的 Thread.join() 方法忿墅。

LockSupport.park() 方法。

限期等待(Timed Waiting):處于這種狀態(tài)的線程也不會被分配 CPU 執(zhí)行時間沮峡,不過無須等待被其他線程顯式地喚醒疚脐,在一定時間之后它們會由系統自動喚醒。以下方法會讓線程進入限期等待狀態(tài):

Thread.sleep() 方法邢疙。

設置了 Timeout 參數的 Object.wait() 方法棍弄。

設置了 Timeout 參數的 Thread.join() 方法。

LockSupport.parkNanos() 方法疟游。

LockSupport.parkUntil() 方法呼畸。

阻塞(Blocked):線程被阻塞了,“阻塞狀態(tài)” 與 “等待狀態(tài)” 的區(qū)別是:“阻塞狀態(tài)” 在等待著獲取到一個排他鎖颁虐,這個事件將在另外一個線程放棄這個鎖的時候發(fā)生蛮原;而 “等待狀態(tài)” 則是在等待一段時間,或者喚醒動作的發(fā)生另绩。在程序等待進入同步區(qū)域的時候儒陨,線程將進入這種狀態(tài)。

結束(Terminated):已終止線程的線程狀態(tài)笋籽,線程已經結束執(zhí)行蹦漠。

??????? 上述 5 種狀態(tài)在遇到特定事件發(fā)生的時候將會互相轉換,它們的轉換關系如圖 12-6 所示车海。

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末笛园,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子容劳,更是在濱河造成了極大的恐慌喘沿,老刑警劉巖闸度,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件竭贩,死亡現場離奇詭異,居然都是意外死亡莺禁,警方通過查閱死者的電腦和手機留量,發(fā)現死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來哟冬,“玉大人楼熄,你說我怎么就攤上這事『葡浚” “怎么了可岂?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長翰灾。 經常有香客問我缕粹,道長稚茅,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任平斩,我火速辦了婚禮亚享,結果婚禮上,老公的妹妹穿的比我還像新娘绘面。我一直安慰自己欺税,他們只是感情好,可當我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布揭璃。 她就那樣靜靜地躺著晚凿,像睡著了一般。 火紅的嫁衣襯著肌膚如雪瘦馍。 梳的紋絲不亂的頭發(fā)上晃虫,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天,我揣著相機與錄音扣墩,去河邊找鬼哲银。 笑死,一個胖子當著我的面吹牛呻惕,可吹牛的內容都是我干的荆责。 我是一名探鬼主播,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼亚脆,長吁一口氣:“原來是場噩夢啊……” “哼做院!你這毒婦竟也來了?” 一聲冷哼從身側響起濒持,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤键耕,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后柑营,有當地人在樹林里發(fā)現了一具尸體屈雄,經...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年官套,在試婚紗的時候發(fā)現自己被綠了酒奶。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡奶赔,死狀恐怖惋嚎,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情站刑,我是刑警寧澤另伍,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站绞旅,受9級特大地震影響摆尝,放射性物質發(fā)生泄漏愕宋。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一结榄、第九天 我趴在偏房一處隱蔽的房頂上張望中贝。 院中可真熱鬧,春花似錦臼朗、人聲如沸邻寿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽绣否。三九已至,卻和暖如春挡毅,著一層夾襖步出監(jiān)牢的瞬間蒜撮,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工跪呈, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留段磨,地道東北人。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓耗绿,卻偏偏與公主長得像苹支,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子误阻,可洞房花燭夜當晚...
    茶點故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內容