中文名 | 線程 | 外文名 | thread |
---|---|---|---|
別????名 | 執(zhí)行緒;輕量進(jìn)程 | 提出者 | dozer |
應(yīng)用學(xué)科 | 計(jì)算機(jī) | 多線程中 | CPU的基本單位 |
定????義 | 程序執(zhí)行流的最小單元 |
進(jìn)程是資源分配的基本單位。所有與該進(jìn)程有關(guān)的資源,都被記錄在進(jìn)程控制塊PCB中。以表示該進(jìn)程擁有這些資源或正在使用它們。
另外,進(jìn)程也是搶占處理機(jī)的調(diào)度單位,它擁有一個(gè)完整的虛擬地址空間。當(dāng)進(jìn)程發(fā)生調(diào)度時(shí),不同的進(jìn)程擁有不同的虛擬地址空間,而同一進(jìn)程內(nèi)的不同線程共享同一地址空間。
與進(jìn)程相對(duì)應(yīng),線程與資源分配無(wú)關(guān),它屬于某一個(gè)進(jìn)程,并與進(jìn)程內(nèi)的其他線程一起共享進(jìn)程的資源。
線程只由相關(guān)堆棧(系統(tǒng)?;蛴脩魲#┘拇嫫骱途€程控制表TCB組成。寄存器可被用來(lái)存儲(chǔ)線程內(nèi)的局部變量,但不能存儲(chǔ)其他線程的相關(guān)變量。
通常在一個(gè)進(jìn)程中可以包含若干個(gè)線程,它們可以利用進(jìn)程所擁有的資源。在引入線程的操作系統(tǒng)中,通常都是把進(jìn)程作為分配資源的基本單位,而把線程作為獨(dú)立運(yùn)行和獨(dú)立調(diào)度的基本單位。由于線程比進(jìn)程更小,基本上不擁有系統(tǒng)資源,故對(duì)它的調(diào)度所付出的開(kāi)銷就會(huì)小得多,能更高效的提高系統(tǒng)內(nèi)多個(gè)程序間并發(fā)執(zhí)行的程度,從而顯著提高系統(tǒng)資源的利用率和吞吐量。因而近年來(lái)推出的通用操作系統(tǒng)都引入了線程,以便進(jìn)一步提高系統(tǒng)的并發(fā)性,并把它視為現(xiàn)代操作系統(tǒng)的一個(gè)重要指標(biāo)。
線程與進(jìn)程的區(qū)別可以歸納為以下4點(diǎn):
1)地址空間和其它資源(如打開(kāi)文件):進(jìn)程間相互獨(dú)立,同一進(jìn)程的各線程間共享。某進(jìn)程內(nèi)的線程在其它進(jìn)程不可見(jiàn)。
2)通信:進(jìn)程間通信IPC,線程間可以直接讀寫(xiě)進(jìn)程數(shù)據(jù)段(如全局變量)來(lái)進(jìn)行通信——需要進(jìn)程同步和互斥手段的輔助,以保證數(shù)據(jù)的一致性。
3)調(diào)度和切換:線程上下文切換比進(jìn)程上下文切換要快得多。
4)在多線程O(píng)S中,進(jìn)程不是一個(gè)可執(zhí)行的實(shí)體。
線程的引入:
60年代,在OS中能擁有資源和獨(dú)立運(yùn)行的基本單位是進(jìn)程,然而隨著計(jì)算機(jī)技術(shù)的發(fā)展,進(jìn)程出現(xiàn)了很多弊端,一是由于進(jìn)程是資源擁有者,創(chuàng)建、撤消與切換存在較大的時(shí)空開(kāi)銷,因此需要引入輕型進(jìn)程;二是由于對(duì)稱多處理機(jī)(SMP)出現(xiàn),可以滿足多個(gè)運(yùn)行單位,而多個(gè)進(jìn)程并行開(kāi)銷過(guò)大。
因此在80年代,出現(xiàn)了能獨(dú)立運(yùn)行的基本單位——線程(Threads)。
在多線程O(píng)S中,通常是在一個(gè)進(jìn)程中包括多個(gè)線程,每個(gè)線程都是作為利用CPU的基本單位,是花費(fèi)最小開(kāi)銷的實(shí)體。線程具有以下屬性。
1)輕型實(shí)體
線程中的實(shí)體基本上不擁有系統(tǒng)資源,只是有一點(diǎn)必不可少的、能保證獨(dú)立運(yùn)行的資源。
線程的實(shí)體包括程序、數(shù)據(jù)和TCB。線程是動(dòng)態(tài)概念,它的動(dòng)態(tài)特性由線程控制塊TCB(Thread Control Block)描述。TCB包括以下信息:
(1)線程狀態(tài)。
(2)當(dāng)線程不運(yùn)行時(shí),被保存的現(xiàn)場(chǎng)資源。
(3)一組執(zhí)行堆棧。
(4)存放每個(gè)線程的局部變量主存區(qū)。
(5)訪問(wèn)同一個(gè)進(jìn)程中的主存和其它資源。
用于指示被執(zhí)行指令序列的程序計(jì)數(shù)器、保留局部變量、少數(shù)狀態(tài)參數(shù)和返回地址等的一組寄存器和堆棧。
2)獨(dú)立調(diào)度和分派的基本單位。
在多線程O(píng)S中,線程是能獨(dú)立運(yùn)行的基本單位,因而也是獨(dú)立調(diào)度和分派的基本單位。由于線程很“輕”,故線程的切換非常迅速且開(kāi)銷?。ㄔ谕贿M(jìn)程中的)。
3)可并發(fā)執(zhí)行。
在一個(gè)進(jìn)程中的多個(gè)線程之間,可以并發(fā)執(zhí)行,甚至允許在一個(gè)進(jìn)程中所有線程都能并發(fā)執(zhí)行;同樣,不同進(jìn)程中的線程也能并發(fā)執(zhí)行,充分利用和發(fā)揮了處理機(jī)與外圍設(shè)備并行工作的能力。
4)共享進(jìn)程資源。
在同一進(jìn)程中的各個(gè)線程,都可以共享該進(jìn)程所擁有的資源,這首先表現(xiàn)在:所有線程都具有相同的地址空間(進(jìn)程的地址空間),這意味著,線程可以訪問(wèn)該地址空間的每一個(gè)虛地址;此外,還可以訪問(wèn)進(jìn)程所擁有的已打開(kāi)文件、定時(shí)器、信號(hào)量機(jī)構(gòu)等。由于同一個(gè)進(jìn)程內(nèi)的線程共享內(nèi)存和文件,所以線程之間互相通信不必調(diào)用內(nèi)核。
這個(gè)應(yīng)該計(jì)算速度比較快的!可能你的電腦配置問(wèn)題導(dǎo)致慢的吧!
多線程計(jì)算可以讓匯總計(jì)算快點(diǎn),但如果你的計(jì)算機(jī)配置不高,使用多線程計(jì)算也沒(méi)有意義。
匯總計(jì)算,勾選就可以啦,老版本沒(méi)有的,新版本才有
(1)創(chuàng)建線程
當(dāng)創(chuàng)建一個(gè)新的進(jìn)程時(shí),也創(chuàng)建一個(gè)新的線程,進(jìn)程中的線程可以在同一進(jìn)程中創(chuàng)建新的線程。
(2)終止線程
可以正常終止自己,也可能某個(gè)線程執(zhí)行錯(cuò)誤,由其它線程強(qiáng)行終止。終止線程操作主要負(fù)責(zé)釋放線程占有的寄存器和棧
(3)阻塞線程
當(dāng)線程等待每個(gè)事件無(wú)法運(yùn)行時(shí),停止其運(yùn)行。
(4)喚醒線程
當(dāng)阻塞線程的事件發(fā)生時(shí),將被阻塞的線程狀態(tài)置為就緒態(tài),將其掛到就緒隊(duì)列。進(jìn)程仍然具有與執(zhí)行相關(guān)的狀態(tài)。例如,所謂進(jìn)程處于“執(zhí)行”狀態(tài),實(shí)際上是指該進(jìn)程中的某線程正在執(zhí)行。對(duì)進(jìn)程施加的與進(jìn)程狀態(tài)有關(guān)的操作,也對(duì)其線程起作用。例如,把某個(gè)進(jìn)程掛起時(shí),該進(jìn)程中的所有線程也都被掛起,激活也是同樣。
線程的同步是Java多線程編程的難點(diǎn),往往開(kāi)發(fā)者搞不清楚什么是競(jìng)爭(zhēng)資源、什么時(shí)候需要考慮同步,怎么同步等等問(wèn)題,當(dāng)然,這些問(wèn)題沒(méi)有很明確的答案,但有些原則問(wèn)題需要考慮,是否有競(jìng)爭(zhēng)資源被同時(shí)改動(dòng)的問(wèn)題?對(duì)于同步,在具體的Java代碼中需要完成以下兩個(gè)操作:把競(jìng)爭(zhēng)訪問(wèn)的資源標(biāo)識(shí)為private;同步哪些修改變量的代碼,使用synchronized關(guān)鍵字同步方法或代碼。當(dāng)然這不是唯一控制并發(fā)安全的途徑。synchronized關(guān)鍵字使用說(shuō)明synchronized只能標(biāo)記非抽象的方法,不能標(biāo)識(shí)成員變量。為了演示同步方法的使用,構(gòu)建了一個(gè)信用卡賬戶,起初信用額為100w,然后模擬透支、存款等多個(gè)操作。顯然銀行賬戶User對(duì)象是個(gè)競(jìng)爭(zhēng)資源,而多個(gè)并發(fā)操作的是賬戶方法oper(int x),當(dāng)然應(yīng)該在此方法上加上同步,并將賬戶的余額設(shè)為私有變量,禁止直接訪問(wèn)。
工作原理
線程是進(jìn)程中的實(shí)體,一個(gè)進(jìn)程可以擁有多個(gè)線程,一個(gè)線程必須有一個(gè)父進(jìn)程。線程不擁有系統(tǒng)資源,只有運(yùn)行必須的一些數(shù)據(jù)結(jié)構(gòu);它與父進(jìn)程的其它線程共享該進(jìn)程所擁有的全部資源。線程可以創(chuàng)建和撤消線程,從而實(shí)現(xiàn)程序的并發(fā)執(zhí)行。一般,線程具有就緒、阻塞和運(yùn)行三種基本狀態(tài)。
在多中央處理器的系統(tǒng)里,不同線程可以同時(shí)在不同的中央處理器上運(yùn)行,甚至當(dāng)它們屬于同一個(gè)進(jìn)程時(shí)也是如此。大多數(shù)支持多處理器的操作系統(tǒng)都提供編程接口來(lái)讓進(jìn)程可以控制自己的線程與各處理器之間的關(guān)聯(lián)度(affinity)。
有時(shí)候,線程也稱作輕量級(jí)進(jìn)程。就象進(jìn)程一樣,線程在程序中是獨(dú)立的、并發(fā)的執(zhí)行路徑,每個(gè)線程有它自己的堆棧、自己的程序計(jì)數(shù)器和自己的局部變量。但是,與分隔的進(jìn)程相比,進(jìn)程中的線程之間的隔離程度要小。它們共享內(nèi)存、文件句柄和其它每個(gè)進(jìn)程應(yīng)有的狀態(tài)。
進(jìn)程可以支持多個(gè)線程,它們看似同時(shí)執(zhí)行,但互相之間并不同步。一個(gè)進(jìn)程中的多個(gè)線程共享相同的內(nèi)存地址空間,這就意味著它們可以訪問(wèn)相同的變量和對(duì)象,而且它們從同一堆中分配對(duì)象。盡管這讓線程之間共享信息變得更容易,但您必須小心,確保它們不會(huì)妨礙同一進(jìn)程里的其它線程。
Java 線程工具和 API看似簡(jiǎn)單。但是,編寫(xiě)有效使用線程的復(fù)雜程序并不十分容易。因?yàn)橛卸鄠€(gè)線程共存在相同的內(nèi)存空間中并共享相同的變量,所以您必須小心,確保您的線程不會(huì)互相干擾。
線程屬性
為了正確有效地使用線程,必須理解線程的各個(gè)方面并了解Java 實(shí)時(shí)系統(tǒng)。必須知道如何提供線程體、線程的生命周期、實(shí)時(shí)系統(tǒng)如 何調(diào)度線程、線程組、什么是幽靈線程(Demo nThread)。
線程體
所有的操作都發(fā)生在線程體中,在Java中線程體是從Thread類繼承的run()方法,或?qū)崿F(xiàn)Runnable接口的類中的run()方法。當(dāng)線程產(chǎn)生并初始化后,實(shí)時(shí)系統(tǒng)調(diào)用它的run()方法。run()方法內(nèi)的代碼實(shí)現(xiàn)所產(chǎn)生線程的行為,它是線程的主要部分。
線程狀態(tài)
附圖表示了線程在它的生命周期內(nèi)的任何時(shí)刻所能處的狀態(tài)以及引起狀態(tài)改變的方法。這圖并不是完整的有限狀態(tài)圖,但基本概括了線程中比較感興趣和普遍的方面。以下討論有關(guān)線程生命周期以此為據(jù)。
●新線程態(tài)(New Thread)
產(chǎn)生一個(gè)Thread對(duì)象就生成一個(gè)新線程。當(dāng)線程處于"新線程"狀態(tài)時(shí),僅僅是一個(gè)空線程對(duì)象,它還沒(méi)有分配到系統(tǒng)資源。因此只能啟動(dòng)或終止它。任何其他操作都會(huì)引發(fā)異常。例如,一個(gè)線程調(diào)用了new方法之后,并在調(diào)用start方法之前的處于新線程狀態(tài),可以調(diào)用start和stop方法。
●可運(yùn)行態(tài)(Runnable)
start()方法產(chǎn)生運(yùn)行線程所必須的資源,調(diào)度線程執(zhí)行,并且調(diào)用線程的run()方法。在這時(shí)線程處于可運(yùn)行態(tài)。該狀態(tài)不稱為運(yùn)行態(tài)是因?yàn)檫@時(shí)的線程并不總是一直占用處理機(jī)。特別是對(duì)于只有一個(gè)處理機(jī)的PC而言,任何時(shí)刻只能有一個(gè)處于可運(yùn)行態(tài)的線程占用處理 機(jī)。Java通過(guò)調(diào)度來(lái)實(shí)現(xiàn)多線程對(duì)處理機(jī)的共享。注意,如果線程處于Runnable狀態(tài),它也有可能不在運(yùn)行,這是因?yàn)檫€有優(yōu)先級(jí)和調(diào)度問(wèn)題。
●阻塞/非運(yùn)行態(tài)(Not Runnable)
當(dāng)以下事件發(fā)生時(shí),線程進(jìn)入非運(yùn)行態(tài)。
①suspend()方法被調(diào)用;
②sleep()方法被調(diào)用;
③線程使用wait()來(lái)等待條件變量;
④線程處于I/O請(qǐng)求的等待。
●死亡態(tài)(Dead)
當(dāng)run()方法返回,或別的線程調(diào)用stop()方法,線程進(jìn)入死亡態(tài)。通常Applet使用它的stop()方法來(lái)終止它產(chǎn)生的所有線程。
線程的本操作:
派生:線程在進(jìn)程內(nèi)派生出來(lái),它即可由進(jìn)程派生,也可由線程派生。
阻塞(Block):如果一個(gè)線程在執(zhí)行過(guò)程中需要等待某個(gè)事件發(fā)生,則被阻塞。
激活(unblock):如果阻塞線程的事件發(fā)生,則該線程被激活并進(jìn)入就緒隊(duì)列。
調(diào)度(schedule):選擇一個(gè)就緒線程進(jìn)入執(zhí)行狀態(tài)。
結(jié)束(Finish):如果一個(gè)線程執(zhí)行結(jié)束,它的寄存器上下文以及堆棧內(nèi)容等將被釋放。
圖2 線程的狀態(tài)與操作
線程的另一個(gè)執(zhí)行特性是同步。線程中所使用的同步控制機(jī)制與進(jìn)程中所使用的同步控制機(jī)制相同。
線程優(yōu)先級(jí)
雖然我們說(shuō)線程是并發(fā)運(yùn)行的。然而事實(shí)常常并非如此。正如前面談到的,當(dāng)系統(tǒng)中只有一個(gè)CPU時(shí),以某種順序在單CPU情況下執(zhí)行多線程被稱為調(diào)度(scheduling)。Java采用的是一種簡(jiǎn)單、固定的調(diào)度法,即固定優(yōu)先級(jí)調(diào)度。這種算法是根據(jù)處于可運(yùn)行態(tài)線程的相對(duì)優(yōu)先級(jí)來(lái)實(shí)行調(diào)度。當(dāng)線程產(chǎn)生時(shí),它繼承原線程的優(yōu)先級(jí)。在需要時(shí)可對(duì)優(yōu)先級(jí)進(jìn)行修改。在任何時(shí)刻,如果有多條線程等待運(yùn)行,系統(tǒng)選擇優(yōu)先級(jí)最高的可運(yùn)行線程運(yùn)行。只有當(dāng)它停止、自動(dòng)放棄、或由于某種原因成為非運(yùn)行態(tài)低優(yōu)先級(jí)的線程才能運(yùn)行。如果兩個(gè)線程具有相同的優(yōu)先級(jí),它們將被交替地運(yùn)行。 Java實(shí)時(shí)系統(tǒng)的線程調(diào)度算法還是強(qiáng)制性的,在任何時(shí)刻,如果一個(gè)比其他線程優(yōu)先級(jí)都高的線程的狀態(tài)變?yōu)榭蛇\(yùn)行態(tài),實(shí)時(shí)系統(tǒng)將選擇該線程來(lái)運(yùn)行。一個(gè)應(yīng)用程序可以通過(guò)使用線程中的方法setPriority(int),來(lái)設(shè)置線程的優(yōu)先級(jí)大小。
有線程進(jìn)入了就緒狀態(tài),需要有線程調(diào)度程序來(lái)決定何時(shí)執(zhí)行,根據(jù)優(yōu)先級(jí)來(lái)調(diào)度。
線程中的join()可以用來(lái)邀請(qǐng)其他線程先執(zhí)行(示例代碼如下):
packageorg.thread.test;publicclassJoin01implementsRunnable{publicstaticvoidmain(String[]args){for(inti=0;i
yield()告訴系統(tǒng)"把自己的CPU時(shí)間讓掉,讓其他線程或者自己運(yùn)行",示例代碼如下:
packageorg.thread.test;
publicclassYield01
{
publicstaticvoidmain(String[]args)
{
YieldFirstyf=newYieldFirst();
YieldSecondys=newYieldSecond();
YieldThirdyt=newYieldThird();
yf.start();ys.start();yt.start();
}
}
classYieldFirstextendsThread
{
@Overridepublicvoidrun()
{
for(inti=0;i
{
System.out.println("第一個(gè)線程第" (i 1) "次運(yùn)行.");//讓當(dāng)前線程暫停yield();
}
}
}
classYieldSecondextendsThread
{
@Overridepublicvoidrun()
{
for(inti=0;i
{
System.out.println("第二個(gè)線程第" (i 1) "次運(yùn)行.");//讓當(dāng)前線程暫停yield();
}
}
classYieldThirdextendsThread
{
@Overridepublicvoidrun(){for(inti=0;i
{
System.out.println("第三個(gè)線程第" (i 1) "次運(yùn)行.");//讓當(dāng)前線程暫停yield();
}
}
幽靈線程
任何一個(gè)Java線程都能成為幽靈線程。它是作為運(yùn)行于同一個(gè)進(jìn)程內(nèi)的對(duì)象和線程的服務(wù)提供者。例如,HotJava瀏覽器有一個(gè)稱為" 后臺(tái)圖片閱讀器"的幽靈線程,它為需要圖片的對(duì)象和線程從文件系統(tǒng)或網(wǎng)絡(luò)讀入圖片?!∮撵`線程是應(yīng)用中典型的獨(dú)立線程。它為同一應(yīng)用中的其他對(duì)象和線程提供服務(wù)。幽靈線程的run()方法一般都是無(wú)限循環(huán),等待服務(wù)請(qǐng)求。
線程組
每個(gè)Java線程都是某個(gè)線程組的成員。線程組提供一種機(jī)制,使得多個(gè)線程集于一個(gè)對(duì)象內(nèi),能對(duì)它們實(shí)行整體操作。譬如,你能用一個(gè)方法調(diào)用來(lái)啟動(dòng)或掛起組內(nèi)的所有線程。Java線程組由ThreadGroup類實(shí)現(xiàn)。
當(dāng)線程產(chǎn)生時(shí),可以指定線程組或由實(shí)時(shí)系統(tǒng)將其放入某個(gè)缺省的線程組內(nèi)。線程只能屬于一個(gè)線程組,并且當(dāng)線程產(chǎn)生后不能改變它所屬的線程組。
多線程
對(duì)于多線程的好處這就不多說(shuō)了。但是,它同樣也帶來(lái)了某些新的麻煩。只要在設(shè)計(jì)程序時(shí)特別小心留意,克服這些麻煩并不算太困難。在生成線程時(shí)必須將線程放在指定的線程組,也可以放在缺省的線程組中,缺省的就是生成該線程的線程所在的線程組。一旦一個(gè)線程加入了某個(gè)線程組,不能被移出這個(gè)組。
同步線程
許多線程在執(zhí)行中必須考慮與其他線程之間共享數(shù)據(jù)或協(xié)調(diào)執(zhí)行狀態(tài)。這就需要同步機(jī)制。在Java中每個(gè)對(duì)象都有一把鎖與之對(duì)應(yīng)。但Java不提供單獨(dú)的lock和unlock操作。它由高層的結(jié)構(gòu)隱式實(shí)現(xiàn),來(lái)保證操作的對(duì)應(yīng)。(然而,我們注意到Java虛擬機(jī)提供單獨(dú)的monito renter和monitorexit指令來(lái)實(shí)現(xiàn)lock和
unlock操作。) synchronized語(yǔ)句計(jì)算一個(gè)對(duì)象引用,試圖對(duì)該對(duì)象完成鎖操作,并且在完成鎖操作前停止處理。當(dāng)鎖操作完成synchronized語(yǔ)句體得到執(zhí)行。當(dāng)語(yǔ)句體執(zhí)行完畢(無(wú)論正?;虍惓#?,解鎖操作自動(dòng)完成。作為面向?qū)ο蟮恼Z(yǔ)言,synchronized經(jīng)常與方法連用。一種比較好的辦法是,如果某個(gè)變量由一個(gè)線程賦值并由別的線程引用或賦值,那么所有對(duì)該變量的訪問(wèn)都必須在某個(gè)synchromized語(yǔ)句或synchronized方法內(nèi)。
在此假設(shè)一種情況:線程1與線程2都要訪問(wèn)某個(gè)數(shù)據(jù)區(qū),并且要求線程1的訪問(wèn)先于線程2,則這時(shí)僅用synchronized是不能解決問(wèn)題的。這在Unix或Windows NT中可用Simaphore來(lái)實(shí)現(xiàn)。而Java并不提供。在Java中提供的是wait()和notify()機(jī)制。使用如下:
synchronizedmethod_1(/*……*/){//calledbythread1.//accessdataareaavailable=true;notify();}synchronizedmethod_2(/*……*/){//calledbythread2.while(!available)try{wait();//waitfornotify().}catch(InterruptedExceptione){}//accessdataarea}
其中available是類成員變量,置初值為false。
如果在method-2中檢查available為假,則調(diào)用wait()。wait()的作用是使線程2進(jìn)入非運(yùn)行態(tài),并且解鎖。在這種情況下,method-1可以被線程1調(diào)用。當(dāng)執(zhí)行notify()后。線程2由非運(yùn)行態(tài)轉(zhuǎn)變?yōu)榭蛇\(yùn)行態(tài)。當(dāng)method-1調(diào)用返回后。線程2可重新對(duì)該對(duì)象加鎖,加鎖成功后執(zhí)行wait()返回后的指令。這種機(jī)制也能適用于其他更復(fù)雜的情況。
死鎖
如果程序中有幾個(gè)競(jìng)爭(zhēng)資源的并發(fā)線程,那么保證均衡是很重要的。系統(tǒng)均衡是指每個(gè)線程在執(zhí)行過(guò)程中都能充分訪問(wèn)有限的資源。系統(tǒng)中沒(méi)有餓死和死鎖的線程。Java并不提供對(duì)死鎖的檢測(cè)機(jī)制。對(duì)大多數(shù)的Java程序員來(lái)說(shuō)防止死鎖是一種較好的選擇。最簡(jiǎn)單的防止死鎖的方法是對(duì)競(jìng)爭(zhēng)的資源引入序號(hào),如果一個(gè)線程需要幾個(gè)資源,那么它必須先得到小序號(hào)的資源,再申請(qǐng)大序號(hào)的資源。
優(yōu)化
Java的多線程安全是基于Lock機(jī)制實(shí)現(xiàn)的,而Lock的性能往往不如人意。原因是,monitorenter與monitorexit這兩個(gè)控制多線程同步的bytecode原語(yǔ),是JVM依賴操作系統(tǒng)互斥(mutex)來(lái)實(shí)現(xiàn)的。而互斥是一種會(huì)導(dǎo)致線程掛起,并在較短的時(shí)間內(nèi)又需要重新調(diào)度回原線程的,較為消耗資源的操作。所以需要進(jìn)行對(duì)線程進(jìn)行優(yōu)化,提高效率。
輕量級(jí)鎖
輕量級(jí)鎖(Lightweight Locking)是從Java6開(kāi)始引入的概念,本意是為了減少多線程進(jìn)入互斥的幾率,并不是要替代互斥。它利用了CPU原語(yǔ)Compare-And-Swap(CAS,匯編指令CMPXCHG),嘗試在進(jìn)入互斥前,進(jìn)行補(bǔ)救。下面將詳細(xì)介紹JVM如何利用CAS,實(shí)現(xiàn)輕量級(jí)鎖。
Java Object Model中定義,Object Header是一個(gè)2字(1 word = 4 byte)長(zhǎng)度的存儲(chǔ)區(qū)域。第一個(gè)字長(zhǎng)度的區(qū)域用來(lái)標(biāo)記同步,GC以及hash code等,官方稱之為 mark word。第二個(gè)字長(zhǎng)度的區(qū)域是指向到對(duì)象的Class。在2個(gè)word中,mark word是輕量級(jí)鎖實(shí)現(xiàn)的關(guān)鍵,其結(jié)構(gòu)見(jiàn)右表。
從表中可以看到,state為lightweight locked的那行即為輕量級(jí)鎖標(biāo)記。bitfieds名為指向lock record的指針,這里的lock record,其實(shí)是一塊分配在線程堆棧上的空間區(qū)域。用于CAS前,拷貝object上的mark word。第三項(xiàng)是重量級(jí)鎖標(biāo)記。后面的狀態(tài)單詞很有趣,inflated,譯為膨脹,在這里意思其實(shí)是鎖已升級(jí)到OS-level。一般我們只關(guān)注第二和第三項(xiàng)即可。lock,unlock與mark word之間的聯(lián)系如右圖所示。在圖中,提到了拷貝object mark word,由于脫離了原始mark word,官方將它冠以displaced前綴,即displaced mark word(置換標(biāo)記字)。這個(gè)displaced mark word是整個(gè)輕量級(jí)鎖實(shí)現(xiàn)的關(guān)鍵,在CAS中的compare就需要用它作為條件。
在拷貝完object mark word之后,JVM做了一步交換指針的操作,即流程中第一個(gè)橙色矩形框內(nèi)容所述。將object mark word里的輕量級(jí)鎖指針指向lock record所在的stack指針,作用是讓其他線程知道,該object monitor已被占用。lock record里的owner指針指向object mark word的作用是為了在接下里的運(yùn)行過(guò)程中,識(shí)別哪個(gè)對(duì)象被鎖住了。
最后一步unlock中,我們發(fā)現(xiàn),JVM同樣使用了CAS來(lái)驗(yàn)證object mark word在持有鎖到釋放鎖之間,有無(wú)被其他線程訪問(wèn)。如果其他線程在持有鎖這段時(shí)間里,嘗試獲取過(guò)鎖,則可能自身被掛起,而mark word的重量級(jí)鎖指針也會(huì)被相應(yīng)修改。此時(shí),unlock后就需要喚醒被掛起的線程。
偏向鎖
Java偏向鎖(Biased Locking)是Java 6引入的一項(xiàng)多線程優(yōu)化。它通過(guò)消除資源無(wú)競(jìng)爭(zhēng)情況下的同步原語(yǔ),進(jìn)一步提高了程序的運(yùn)行性能。它與輕量級(jí)鎖的區(qū)別在于,輕量級(jí)鎖是通過(guò)CAS來(lái)避免進(jìn)入開(kāi)銷較大的互斥操作,而偏向鎖是在無(wú)競(jìng)爭(zhēng)場(chǎng)景下完全消除同步,連CAS也不執(zhí)行(CAS本身仍舊是一種操作系統(tǒng)同步原語(yǔ),始終要在JVM與OS之間來(lái)回,有一定的開(kāi)銷)。所謂的無(wú)競(jìng)爭(zhēng)場(chǎng)景,就是單線程訪問(wèn)帶同步的資源或方法。
偏向鎖,顧名思義,它會(huì)偏向于第一個(gè)訪問(wèn)鎖的線程,如果在接下來(lái)的運(yùn)行過(guò)程中,該鎖沒(méi)有被其他的線程訪問(wèn),則持有偏向鎖的線程將永遠(yuǎn)不需要觸發(fā)同步。如果在運(yùn)行過(guò)程中,遇到了其他線程搶占鎖,則持有偏向鎖的線程會(huì)被掛起,JVM會(huì)嘗試消除它身上的偏向鎖,將鎖恢復(fù)到標(biāo)準(zhǔn)的輕量級(jí)鎖。(偏向鎖只能在單線程下起作用)。
偏向模式和非偏向模式,在mark word表中,主要體現(xiàn)在thread ID字段是否為空。
掛起持有偏向鎖的線程,這步操作類似GC的pause,但不同之處是,它只掛起持有偏向鎖的線程(非當(dāng)前線程)。
在搶占模式的橙色區(qū)域說(shuō)明中有提到,指向當(dāng)前堆棧中最近的一個(gè)lock record(在輕量級(jí)鎖中,lock record是進(jìn)入鎖前會(huì)在stack上創(chuàng)建的一份內(nèi)存空間)。這里提到的最近的一個(gè)lock record,其實(shí)就是當(dāng)前鎖所在的stack frame上分配的lock record。整個(gè)步驟是從偏向鎖恢復(fù)到輕量級(jí)鎖的過(guò)程。
偏向鎖也會(huì)帶來(lái)額外開(kāi)銷。在JDK6中,偏向鎖是默認(rèn)啟用的。它提高了單線程訪問(wèn)同步資源的性能。
但試想一下,如果你的同步資源或代碼一直都是多線程訪問(wèn)的,那么消除偏向鎖這一步驟對(duì)你來(lái)說(shuō)就是多余的。事實(shí)上,消除偏向鎖的開(kāi)銷還是蠻大的。所以在你非常熟悉自己的代碼前提下,大可禁用偏向鎖 -XX:-UseBiasedLocking。
分類
線程有兩個(gè)基本類型:
用戶級(jí)線程:管理過(guò)程全部由用戶程序完成,操作系統(tǒng)內(nèi)核心只對(duì)進(jìn)程進(jìn)行管理。
系統(tǒng)級(jí)線程(核心級(jí)線程):由操作系統(tǒng)內(nèi)核進(jìn)行管理。操作系統(tǒng)內(nèi)核給應(yīng)用程序提供相應(yīng)的系統(tǒng)調(diào)用和應(yīng)用程序接口API,以使用戶程序可以創(chuàng)建、執(zhí)行、撤消線程。
舉例UNIX International 線程
UNIX International 線程的頭文件是 ,僅適用于Sun Solaris操作系統(tǒng)。所以UNIX International線程也常被俗稱為Solaris線程。
1.創(chuàng)建線程
intthr_create(void*stack_base,size_tstack_size,void*(*start_routine)(void*),void*arg,longflags,thread_t*new_thr);
2.等待線程
intthr_join(thread_twait_for,thread_t*dead,void**status);
3.掛起線程
intthr_suspend(thread_tthr);
4.繼續(xù)線程
intthr_continue(thread_tthr);
5.退出線程
voidthr_exit(void*status);
6.返回當(dāng)前線程的線程標(biāo)識(shí)符
thread_tthr_self(void);POSIX線程
POSIX線程(Pthreads)的頭文件是,適用于類Unix操作系統(tǒng)。Windows操作系統(tǒng)并沒(méi)有對(duì)POSIX線程提供原生的支持庫(kù)。不過(guò)Win32的POSIX線程庫(kù)的一些實(shí)現(xiàn)也還是有的,例如pthreads-w32 。
1.創(chuàng)建線程
intpthread_create(pthread_t*thread,constpthread_attr_t*attr,void*(*start_routine)(void*),void*arg);
2.等待線程
intpthread_join(pthread_tthread,void**retval);
3.退出線程
voidpthread_exit(void*retval);
4.返回當(dāng)前線程的線程標(biāo)識(shí)符
pthread_tpthread_self(void);
5.線程取消
intpthread_cancel(pthread_tthread);Win32線程
Win32線程的頭文件是,適用于Windows操作系統(tǒng)。
1.創(chuàng)建線程
HANDLEWINAPICreateThread(LPSECURITY_ATTRIBUTESlpThreadAttributes,SIZE_TdwStackSize,LPTHREAD_START_ROUTINElpStartAddress,LPVOIDlpParameter,DWORDdwCreationFlags,LPDWORDlpThreadId);
2.結(jié)束本線程
VOIDWINAPIExitThread(DWORDdwExitCode);
3.掛起指定的線程
DWORDWINAPISuspendThread(HANDLEhThread);
4.恢復(fù)指定線程運(yùn)行
DWORDWINAPIResumeThread(HANDLEhThread);
5.等待線程運(yùn)行完畢
DWORDWINAPIWaitForSingleObject(HANDLEhHandle,DWORDdwMilliseconds);
6.返回當(dāng)前線程的線程標(biāo)識(shí)符
DWORDWINAPIGetCurrentThreadId(void);
7.返回當(dāng)前線程的線程句柄
HANDLEWINAPIGetCurrentThread(void);C 11 線程
C 11 線程的頭文件是。
創(chuàng)建線程std::thread::thread(Function&& f, Args&&... args);
等待線程結(jié)束std::thread::join();
脫離線程控制std::thread::detach();
交換線程std::thread::swap( thread& other );
C 11 線程
C11線程的頭文件是。
C11線程僅僅是個(gè)“建議標(biāo)準(zhǔn)”,也就是說(shuō)100%遵守C11標(biāo)準(zhǔn)的C編譯器是可以不支持C11線程的。根據(jù)C11標(biāo)準(zhǔn)的規(guī)定,只要編譯器預(yù)定義了__STDC_NO_THREADS__宏,就可以沒(méi)有頭文件,自然也就也沒(méi)有下列函數(shù)。
1.創(chuàng)建線程
intthrd_create(thrd_t*thr,thrd_start_tfunc,void*arg);
2.結(jié)束本線程
_Noreturnvoidthrd_exit(intres);
3.等待線程運(yùn)行完畢
intthrd_join(thrd_tthr,int*res);
4.返回當(dāng)前線程的線程標(biāo)識(shí)符
thrd_tthrd_current();Java線程
1)最簡(jiǎn)單的情況是,Thread/Runnable的run()方法運(yùn)行完畢,自行終止。
2)對(duì)于更復(fù)雜的情況,比如有循環(huán),則可以增加終止標(biāo)記變量和任務(wù)終止的檢查點(diǎn)。
3)最常見(jiàn)的情況,也是為了解決阻塞不能執(zhí)行檢查點(diǎn)的問(wèn)題,用中斷來(lái)結(jié)束線程,但中斷只是請(qǐng)求,并不能完全保證線程被終止,需要執(zhí)行線程協(xié)同處理。
4)IO阻塞和等鎖情況下需要通過(guò)特殊方式進(jìn)行處理。
5)使用Future類的cancel()方法調(diào)用。
6)調(diào)用線程池執(zhí)行器的shutdown()和shutdownNow()方法。
7)守護(hù)線程會(huì)在非守護(hù)線程都結(jié)束時(shí)自動(dòng)終止。
8)Thread的stop()方法,但已不推薦使用。
線程的組成
1)一組代表處理器狀態(tài)的CPU寄存器中的內(nèi)容
2)兩個(gè)棧,一個(gè)用于當(dāng)線程在內(nèi)核模式下執(zhí)行的時(shí)候,另一個(gè)用于線程在用戶模式下執(zhí)行的時(shí)候
3)一個(gè)被稱為線程局部存儲(chǔ)器(TLS,thread-local storage)的私有儲(chǔ)存區(qū)域,各個(gè)子系統(tǒng)、運(yùn)行庫(kù)和DLL都會(huì)用到該儲(chǔ)存區(qū)域
4)一個(gè)被稱為線程ID(thread ID,線程標(biāo)識(shí)符)的唯一標(biāo)識(shí)符(在內(nèi)部也被稱為客戶ID——進(jìn)程ID和線程ID是在同一個(gè)名字空間中生產(chǎn)的,所以它們永遠(yuǎn) 不會(huì)重疊)
5)有時(shí)候線程也有它們自己的安全環(huán)境,如果多線程服務(wù)器應(yīng)用程序要模仿其客戶的安全環(huán)境,則往往可以利用線程的安全環(huán)境
1.服務(wù)器中的文件管理或通信控制
2.前后臺(tái)處理
3.異步處理
守護(hù)線程是特殊的線程,一般用于在后臺(tái)為其他線程提供服務(wù).
Java中,isDaemon():判斷一個(gè)線程是否為守護(hù)線程.
Java中,setDaemon():設(shè)置一個(gè)線程為守護(hù)線程.
C# 守護(hù)線程
類1:守護(hù)線程類
/** * 本線程設(shè)置了一個(gè)超時(shí)時(shí)間 * 該線程開(kāi)始運(yùn)行后,經(jīng)過(guò)指定超時(shí)時(shí)間, * 該線程會(huì)拋出一個(gè)未檢查異常通知調(diào)用該線程的程序超時(shí) * 在超時(shí)結(jié)束前可以調(diào)用該類的cancel方法取消計(jì)時(shí) * @author solonote */public class TimeoutThread extends Thread{ /** * 計(jì)時(shí)器超時(shí)時(shí)間 */ private long timeout; /** * 計(jì)時(shí)是否被取消 */ private boolean isCanceled = false; /** * 當(dāng)計(jì)時(shí)器超時(shí)時(shí)拋出的異常 */ private TimeoutException timeoutException; /** * 構(gòu)造器 * @param timeout 指定超時(shí)的時(shí)間 */ public TimeoutThread(long timeout,TimeoutException timeoutErr) { super(); this.timeout = timeout; this.timeoutException = timeoutErr; //設(shè)置本線程為守護(hù)線程 this.setDaemon(true); } /** * 取消計(jì)時(shí) */ public synchronized void cancel() { isCanceled = true; } /** * 啟動(dòng)超時(shí)計(jì)時(shí)器 */ public void run() { try { Thread.sleep(timeout); if(!isCanceled) throw timeoutException; } catch (InterruptedException e) { e.printStackTrace(); } } }
格式:pdf
大?。?span id="r2ob0ns" class="single-tag-height">466KB
頁(yè)數(shù): 未知
評(píng)分: 4.8
基于多態(tài)并行處理器提出了一種硬件線程管理器,支持MIMD模式8個(gè)線程管理操作和SIMD模式SC控制器統(tǒng)一管理兩種工作模式,實(shí)現(xiàn)了線程級(jí)并行計(jì)算;可以監(jiān)測(cè)各個(gè)線程的工作情況以及近鄰?fù)ㄐ偶拇嫫骱吐酚善鞯臓顟B(tài);能夠在通信時(shí)停止、切換、啟動(dòng)線程,記錄每個(gè)線程的工作狀態(tài),同時(shí)避免了因數(shù)據(jù)阻塞帶來(lái)的等待問(wèn)題,能夠最大程度地提高單個(gè)處理器的執(zhí)行效率。
格式:pdf
大?。?span id="yw6vtz6" class="single-tag-height">466KB
頁(yè)數(shù): 4頁(yè)
評(píng)分: 4.5
濕能空調(diào)機(jī)組是一種可提供全新風(fēng)的新型空調(diào)設(shè)備。目前濕能空調(diào)機(jī)組的性能檢測(cè)系統(tǒng)已開(kāi)發(fā)成功。介紹了性能檢測(cè)系統(tǒng)的體系結(jié)構(gòu)。測(cè)控軟件采用基于Delphi平臺(tái)的多線程模式,并實(shí)現(xiàn)相應(yīng)的功能。運(yùn)行結(jié)果表明,檢測(cè)系統(tǒng)的測(cè)試精度達(dá)到了國(guó)家標(biāo)準(zhǔn)的要求,測(cè)控軟件的性能完善,工作可靠。
//線程池示例 usingSystem; usingSystem.Threading;publicclassTest { //存放要計(jì)算的數(shù)值的字段 staticdoublenumber1=-1; staticdoublenumber2=-1;publicstaticvoidMain() { //獲取線程池的最大線程數(shù)和維護(hù)的最小空閑線程數(shù) intmaxThreadNum,minThreadNum; intportThreadNum; ThreadPool.GetMaxThreads(outmaxThreadNum,outportThreadNum); ThreadPool.GetMinThreads(outminThreadNum,outportThreadNum); Console.WriteLine("最大線程數(shù):{0}",maxThreadNum); Console.WriteLine("最小線程數(shù):{0}",minThreadNum); //函數(shù)變量值 intx=15600; //啟動(dòng)第一個(gè)任務(wù):計(jì)算x的8次方 Console.WriteLine("啟動(dòng)第一個(gè)任務(wù):計(jì)算{0}的8次方。",x); ThreadPool.QueueUserWorkItem(newWaitCallback(TaskProc1),x); //啟動(dòng)第二個(gè)任務(wù):計(jì)算x的8次方根 Console.WriteLine("啟動(dòng)第二個(gè)任務(wù):計(jì)算{0}的8次方根。",x); ThreadPool.QueueUserWorkItem(newWaitCallback(TaskProc2),x); //等待,直到兩個(gè)數(shù)值都完成計(jì)算 while(number1==-1||number2==-1); //打印計(jì)算結(jié)果 Console.WriteLine("y({0})={1}",x,number1 number2); Console.Read(); } //啟動(dòng)第一個(gè)任務(wù):計(jì)算x的8次方 staticvoidTaskProc1(objecto) { number1=Math.Pow(Convert.ToDouble(o),8); } //啟動(dòng)第二個(gè)任務(wù):計(jì)算x的8次方根 staticvoidTaskProc2(objecto) { number2=Math.Pow(Convert.ToDouble(o),1.0/8.0); } }
[HostProtection(SecurityAction.LinkDemand,Synchronization=true,ExternalThreading=true)]publicstaticclassThreadPool { [Obsolete("ThreadPool.BindHandle(IntPtr)hasbeendeprecated.PleaseuseThreadPool.BindHandle(SafeHandle)instead.",false),SecurityPermission(SecurityAction.Demand,Flags=SecurityPermissionFlag.UnmanagedCode)] publicstaticboolBindHandle(IntPtrosHandle) { if(osHandle==null){thrownewArgumentNullException("osHandle");} boolflag=false; boolsuccess=false; RuntimeHelpers.PrepareConstrainedRegions(); try { osHandle.DangerousAddRef(refsuccess); flag=BindIOCompletionCallbackNative(osHandle.DangerousGetHandle()); } finally { if(success) osHandle.DangerousRelease(); } returnflag; }
線程池(英語(yǔ):thread pool):一種線程使用模式。線程過(guò)多會(huì)帶來(lái)調(diào)度開(kāi)銷,進(jìn)而影響緩存局部性和整體性能。而線程池維護(hù)著多個(gè)線程,等待著監(jiān)督管理者分配可并發(fā)執(zhí)行的任務(wù)。這避免了在處理短時(shí)間任務(wù)時(shí)創(chuàng)建與銷毀線程的代價(jià)。線程池不僅能夠保證內(nèi)核的充分利用,還能防止過(guò)分調(diào)度??捎镁€程數(shù)量應(yīng)該取決于可用的并發(fā)處理器、處理器內(nèi)核、內(nèi)存、網(wǎng)絡(luò)sockets等的數(shù)量。 例如,線程數(shù)一般取cpu數(shù)量 2比較合適,線程數(shù)過(guò)多會(huì)導(dǎo)致額外的線程切換開(kāi)銷。
任務(wù)調(diào)度以執(zhí)行線程的常見(jiàn)方法是使用同步隊(duì)列,稱作任務(wù)隊(duì)列。池中的線程等待隊(duì)列中的任務(wù),并把執(zhí)行完的任務(wù)放入完成隊(duì)列中。
線程池模式一般分為兩種:HS/HA半同步/半異步模式、L/F領(lǐng)導(dǎo)者與跟隨者模式。
半同步/半異步模式又稱為生產(chǎn)者消費(fèi)者模式,是比較常見(jiàn)的實(shí)現(xiàn)方式,比較簡(jiǎn)單。分為同步層、隊(duì)列層、異步層三層。同步層的主線程處理工作任務(wù)并存入工作隊(duì)列,工作線程從工作隊(duì)列取出任務(wù)進(jìn)行處理,如果工作隊(duì)列為空,則取不到任務(wù)的工作線程進(jìn)入掛起狀態(tài)。由于線程間有數(shù)據(jù)通信,因此不適于大數(shù)據(jù)量交換的場(chǎng)合。
領(lǐng)導(dǎo)者跟隨者模式,在線程池中的線程可處在3種狀態(tài)之一:領(lǐng)導(dǎo)者leader、追隨者follower或工作者processor。任何時(shí)刻線程池只有一個(gè)領(lǐng)導(dǎo)者線程。事件到達(dá)時(shí),領(lǐng)導(dǎo)者線程負(fù)責(zé)消息分離,并從處于追隨者線程中選出一個(gè)來(lái)當(dāng)繼任領(lǐng)導(dǎo)者,然后將自身設(shè)置為工作者狀態(tài)去處置該事件。處理完畢后工作者線程將自身的狀態(tài)置為追隨者。這一模式實(shí)現(xiàn)復(fù)雜,但避免了線程間交換任務(wù)數(shù)據(jù),提高了CPU cache相似性。在ACE(Adaptive Communication Environment)中,提供了領(lǐng)導(dǎo)者跟隨者模式實(shí)現(xiàn)。
線程池的伸縮性對(duì)性能有較大的影響。
創(chuàng)建太多線程,將會(huì)浪費(fèi)一定的資源,有些線程未被充分使用。
銷毀太多線程,將導(dǎo)致之后浪費(fèi)時(shí)間再次創(chuàng)建它們。
創(chuàng)建線程太慢,將會(huì)導(dǎo)致長(zhǎng)時(shí)間的等待,性能變差。
銷毀線程太慢,導(dǎo)致其它線程資源饑餓。
應(yīng)用程序可以有多個(gè)線程,這些線程在休眠狀態(tài)中需要耗費(fèi)大量時(shí)間來(lái)等待事件發(fā)生。其他線程可能進(jìn)入睡眠狀態(tài),并且僅定期被喚醒以輪循更改或更新?tīng)顟B(tài)信息,然后再次進(jìn)入休眠狀態(tài)。為了簡(jiǎn)化對(duì)這些線程的管理,.NET框架為每個(gè)進(jìn)程提供了一個(gè)線程池,一個(gè)線程池有若干個(gè)等待操作狀態(tài),當(dāng)一個(gè)等待操作完成時(shí),線程池中的輔助線程會(huì)執(zhí)行回調(diào)函數(shù)。線程池中的線程由系統(tǒng)管理,程序員不需要費(fèi)力于線程管理,可以集中精力處理應(yīng)用程序任務(wù)。