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