多線程系列教程: java多線程-概念&創(chuàng)建啟動&中斷&守護線程&優(yōu)先級&線程狀態(tài)(多線程編程之一)java多線程同步以及線程間通信詳解&消費者生產(chǎn)者模式&死鎖&Thread.join()(多線程編程之二) java&android線程池-Executor框架之ThreadPoolExcutor&ScheduledThreadPoolExecutor淺析(多線程編程之三) Java多線程:Callable、Future和FutureTask淺析(多線程編程之四)
今天開始就來總結(jié)一下java多線程的基礎(chǔ)知識點,下面是本篇的主要內(nèi)容(大部分知識點參考java核心技術(shù)卷1):
1.什么是線程以及多線程與進程的區(qū)別
2.多線程的創(chuàng)建與啟動
3.中斷線程和守護線程以及線程優(yōu)先級
4.線程的狀態(tài)轉(zhuǎn)化關(guān)系
1.什么是線程以及多線程與進程的區(qū)別
在現(xiàn)代操作在運行一個程序時,會為其創(chuàng)建一個進程。例如啟動一個QQ程序,操作系統(tǒng)就會為其創(chuàng)建一個進程。而操作系統(tǒng)中調(diào)度的最小單位元是線程,也叫輕量級進程,在一個進程里可以創(chuàng)建多個線程,這些線程都擁有各自的計數(shù)器,堆棧和局部變量等屬性,并且能夠訪問共享的內(nèi)存變量。處理器在這些線程上高速切換,讓使用者感覺到這些線程在同時執(zhí)行。因此我們可以這樣理解:
進程:正在運行的程序,是系統(tǒng)進行資源分配和調(diào)用的獨立單位。每一個進程都有它自己的內(nèi)存空間和系統(tǒng)資源。
線程:是進程中的單個順序控制流,是一條執(zhí)行路徑一個進程如果只有一條執(zhí)行路徑,則稱為單線程程序。一個進程如果有多條執(zhí)行路徑,則稱為多線程程序。
2.多線程的創(chuàng)建與啟動
創(chuàng)建多線程有兩種方法,一種是繼承Thread類重寫run方法,另一種是實現(xiàn)Runnable接口重寫run方法。下面我們分別給出代碼示例,繼承Thread類重寫run方法:
代碼相當簡單,不過多解釋。這里有點需要注意的是調(diào)用start()方法后并不是是立即的執(zhí)行多線程的代碼,而是使該線程變?yōu)榭蛇\行態(tài),什么時候運行多線程代碼是由操作系統(tǒng)決定的。
3.中斷線程和守護線程以及線程優(yōu)先級
什么是中斷線程?
我們先來看看中斷線程是什么?(該解釋來自java核心技術(shù)一書,我對其進行稍微簡化),當線程的run()方法執(zhí)行方法體中的最后一條語句后,并經(jīng)由執(zhí)行return語句返回時,或者出現(xiàn)在方法中沒有捕獲的異常時線程將終止。在java早期版本中有一個stop方法,其他線程可以調(diào)用它終止線程,但是這個方法現(xiàn)在已經(jīng)被棄用了,因為這個方法會造成一些線程不安全的問題。我們可以把中斷理解為一個標識位的屬性,它表示一個運行中的線程是否被其他線程進行了中斷操作,而中斷就好比其他線程對該線程打可個招呼,其他線程通過調(diào)用該線程的interrupt方法對其進行中斷操作,當一個線程調(diào)用interrupt方法時,線程的中斷狀態(tài)(標識位)將被置位(改變),這是每個線程都具有的boolean標志,每個線程都應(yīng)該不時的檢查這個標志,來判斷線程是否被中斷。而要判斷線程是否被中斷,我們可以使用如下代碼
同時還有點要注意的就是我們在捉中斷異常時盡量按如下形式處理,不要留空白什么都不處理!
不妥的處理方式:
最后關(guān)于中斷線程,我們這里給出中斷線程的一些主要方法:
void interrupt():向線程發(fā)送中斷請求,線程的中斷狀態(tài)將會被設(shè)置為true,如果當前線程被一個sleep調(diào)用阻塞,那么將會拋出interrupedException異常。
static boolean interrupted():測試當前線程(當前正在執(zhí)行命令的這個線程)是否被中斷。注意這是個靜態(tài)方法,調(diào)用這個方法會產(chǎn)生一個副作用那就是它會將當前線程的中斷狀態(tài)重置為false。
boolean isInterrupted():判斷線程是否被中斷,這個方法的調(diào)用不會產(chǎn)生副作用即不改變線程的當前中斷狀態(tài)。
static Thread currentThread() : 返回代表當前執(zhí)行線程的Thread對象。
什么是守護線程?
首先我們可以通過t.setDaemon(true)的方法將線程轉(zhuǎn)化為守護線程。而守護線程的唯一作用就是為其他線程提供服務(wù)。計時線程就是一個典型的例子,它定時地發(fā)送“計時器滴答”信號告訴其他線程去執(zhí)行某項任務(wù)。當只剩下守護線程時,虛擬機就退出了,因為如果只剩下守護線程,程序就沒有必要執(zhí)行了。另外JVM的垃圾回收、內(nèi)存管理等線程都是守護線程。還有就是在做數(shù)據(jù)庫應(yīng)用時候,使用的數(shù)據(jù)庫連接池,連接池本身也包含著很多后臺線程,監(jiān)控連接個數(shù)、超時時間、狀態(tài)等等。最后還有一點需要特別注意的是在java虛擬機退出時Daemon線程中的finally代碼塊并不一定會執(zhí)行哦,代碼示例:
因此在構(gòu)建Daemon線程時,不能依靠finally代碼塊中的內(nèi)容來確保執(zhí)行關(guān)閉或清理資源的邏輯。
什么是線程優(yōu)先級
在現(xiàn)代操作系統(tǒng)中基本采用時分的形式調(diào)度運行的線程,操作系統(tǒng)會分出一個個時間片,線程會分配到若干時間片,當線程的時間片用完了就會發(fā)生線程調(diào)度,并等待著下一次分配。線程分配到的時間片多少也決定了線程使用處理器資源的多少,而線程優(yōu)先級就是決定線程需要多或者少分配一些處理器資源的線程屬性。在java線程中,通過一個整型的成員變量Priority來控制線程優(yōu)先級,每一個線程有一個優(yōu)先級,默認情況下,一個線程繼承它父類的優(yōu)先級??梢杂胹etPriority方法提高或降低任何一個線程優(yōu)先級??梢詫?yōu)先級設(shè)置在MIN_PRIORITY(在Thread類定義為1)與MAX_PRIORITY(在Thread類定義為10)之間的任何值。線程的默認優(yōu)先級為NORM_PRIORITY(在Thread類定義為5)。盡量不要依賴優(yōu)先級,如果確實要用,應(yīng)該避免初學(xué)者常犯的一個錯誤。如果有幾個高優(yōu)先級的線程沒有進入非活動狀態(tài),低優(yōu)先級線程可能永遠也不能執(zhí)行。每當調(diào)度器決定運行一個新線程時,首先會在具有高優(yōu)先級的線程中進行選擇,盡管這樣會使低優(yōu)先級的線程可能永遠不會被執(zhí)行到。因此我們在設(shè)置優(yōu)先級時,針對頻繁阻塞(休眠或者I/O操作)的線程需要設(shè)置較高的優(yōu)先級,而偏重計算(需要較多CPU時間或者運算)的線程則設(shè)置較低的優(yōu)先級,這樣才能確保處理器不會被長久獨占。當然還有要注意就是在不同的JVM以及操作系統(tǒng)上線程的規(guī)劃存在差異,有些操作系統(tǒng)甚至?xí)雎詫€程優(yōu)先級的設(shè)定,如mac
os系統(tǒng)或者Ubuntu系統(tǒng)........
4.線程的狀態(tài)轉(zhuǎn)化關(guān)系
(1). 新建狀態(tài)(New):新創(chuàng)建了一個線程對象。
(2). 就緒狀態(tài)(Runnable):線程對象創(chuàng)建后,其他線程調(diào)用了該對象的start()方法。該狀態(tài)的線程位于可運行線程池中,變得可運行,等待獲取CPU的使用權(quán)。
(3). 運行狀態(tài)(Running):就緒狀態(tài)的線程獲取了CPU,執(zhí)行程序代碼。
(4). 阻塞狀態(tài)(Blocked):阻塞狀態(tài)是線程因為某種原因放棄CPU使用權(quán),暫時停止運行。直到線程進入就緒狀態(tài),才有機會轉(zhuǎn)到運行狀態(tài)。阻塞的情況分三種:
- 等待阻塞(WAITING):運行的線程執(zhí)行wait()方法,JVM會把該線程放入等待池中。 - 同步阻塞(Blocked):運行的線程在獲取對象的同步鎖時,若該同步鎖被別的線程占用,則JVM會把該線程放入鎖池中。 - 超時阻塞(TIME_WAITING):運行的線程執(zhí)行sleep(long)或join(long)方法,或者發(fā)出了I/O請求時,JVM會把該線程置為阻塞狀態(tài)。 (5). 死亡狀態(tài)(Dead):線程執(zhí)行完了或者因異常退出了run()方法,該線程結(jié)束生命周期。
圖中的方法解析如下:
Thread.sleep():在指定時間內(nèi)讓當前正在執(zhí)行的線程暫停執(zhí)行,但不會釋放"鎖標志"。不推薦使用。
Thread.sleep(long):使當前線程進入阻塞狀態(tài),在指定時間內(nèi)不會執(zhí)行。
Object.wait()和Object.wait(long):在其他線程調(diào)用對象的notify或notifyAll方法前,導(dǎo)致當前線程等待。線程會釋放掉它所占有的"鎖標志",從而使別的線程有機會搶占該鎖。 當前線程必須擁有當前對象鎖。如果當前線程不是此鎖的擁有者,會拋出IllegalMonitorStateException異常。
喚醒當前對象鎖的等待線程使用notify或notifyAll方法,也必須擁有相同的對象鎖,否則也會拋出IllegalMonitorStateException異常,waite()和notify()必須在synchronized函數(shù)或synchronized中進行調(diào)用。如果在non-synchronized函數(shù)或non-synchronized中進行調(diào)用,雖然能編譯通過,但在運行時會發(fā)生IllegalMonitorStateException的異常。
Object.notifyAll():則從對象等待池中喚醒所有等待等待線程
Object.notify():則從對象等待池中喚醒其中一個線程。
Thread.yield()方法 暫停當前正在執(zhí)行的線程對象,yield()只是使當前線程重新回到可執(zhí)行狀態(tài),所以執(zhí)行yield()的線程有可能在進入到可執(zhí)行狀態(tài)后馬上又被執(zhí)行,yield()只能使同優(yōu)先級或更高優(yōu)先級的線程有執(zhí)行的機會。
Thread.Join():把指定的線程加入到當前線程,可以將兩個交替執(zhí)行的線程合并為順序執(zhí)行的線程。比如在線程B中調(diào)用了線程A的Join()方法,直到線程A執(zhí)行完畢后,才會繼續(xù)執(zhí)行線程B。
好了。本篇線程基礎(chǔ)知識介紹到此結(jié)束。
|
|