溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Java高級-解析Java中的多線程機制(轉(zhuǎn))

發(fā)布時間:2020-08-10 22:35:17 來源:ITPUB博客 閱讀:152 作者:BSDLite 欄目:編程語言
Java高級-解析Java中的多線程機制(轉(zhuǎn))[@more@]進程與應(yīng)用程序的區(qū)別



進程(Process)是最初定義在Unix等多用戶、多任務(wù)操作系統(tǒng)環(huán)境下用于表示應(yīng)用程序在內(nèi)存環(huán)境中基本執(zhí)行單元的概念。以Unix操作系統(tǒng)為例,進程是Unix操作系統(tǒng)環(huán)境中的基本成分、是系統(tǒng)資源分配的基本單位。Unix操作系統(tǒng)中完成的幾乎所有用戶管理和資源分配等工作都是通過操作系統(tǒng)對應(yīng)用程序進程的控制來實現(xiàn)的。

C、C++、Java等語言編寫的源程序經(jīng)相應(yīng)的編譯器編譯成可執(zhí)行文件后,提交給計算機處理器運行。這時,處在可執(zhí)行狀態(tài)中的應(yīng)用程序稱為進程。從用戶角度來看,進程是應(yīng)用程序的一個執(zhí)行過程。從操作系統(tǒng)核心角度來看,進程代表的是操作系統(tǒng)分配的內(nèi)存、CPU時間片等資源的基本單位,是為正在運行的程序提供的運行環(huán)境。進程與應(yīng)用程序的區(qū)別在于應(yīng)用程序作為一個靜態(tài)文件存儲在計算機系統(tǒng)的硬盤等存儲空間中,而進程則是處于動態(tài)條件下由操作系統(tǒng)維護的系統(tǒng)資源管理實體。多任務(wù)環(huán)境下應(yīng)用程序進程的主要特點包括:

●進程在執(zhí)行過程中有內(nèi)存單元的初始入口點,并且進程存活過程中始終擁有獨立的內(nèi)存地址空間;

●進程的生存期狀態(tài)包括創(chuàng)建、就緒、運行、阻塞和死亡等類型;

●從應(yīng)用程序進程在執(zhí)行過程中向CPU發(fā)出的運行指令形式不同,可以將進程的狀態(tài)分為用戶態(tài)和核心態(tài)。處于用戶態(tài)下的進程執(zhí)行的是應(yīng)用程序指令、處于核心態(tài)下的應(yīng)用程序進程執(zhí)行的是操作系統(tǒng)指令。

在Unix操作系統(tǒng)啟動過程中,系統(tǒng)自動創(chuàng)建swapper、init等系統(tǒng)進程,用于管理內(nèi)存資源以及對用戶進程進行調(diào)度等。在Unix環(huán)境下無論是由操作系統(tǒng)創(chuàng)建的進程還要由應(yīng)用程序執(zhí)行創(chuàng)建的進程,均擁有唯一的進程標(biāo)識(PID)。

進程與Java線程的區(qū)別

應(yīng)用程序在執(zhí)行過程中存在一個內(nèi)存空間的初始入口點地址、一個程序執(zhí)行過程中的代碼執(zhí)行序列以及用于標(biāo)識進程結(jié)束的內(nèi)存出口點地址,在進程執(zhí)行過程中的每一時間點均有唯一的處理器指令與內(nèi)存單元地址相對應(yīng)。

Java語言中定義的線程(Thread)同樣包括一個內(nèi)存入口點地址、一個出口點地址以及能夠順序執(zhí)行的代碼序列。但是進程與線程的重要區(qū)別在于線程不能夠單獨執(zhí)行,它必須運行在處于活動狀態(tài)的應(yīng)用程序進程中,因此可以定義線程是程序內(nèi)部的具有并發(fā)性的順序代碼流。

Unix 操作系統(tǒng)和Microsoft Windows操作系統(tǒng)支持多用戶、多進程的并發(fā)執(zhí)行,而Java語言支持應(yīng)用程序進程內(nèi)部的多個執(zhí)行線程的并發(fā)執(zhí)行。多線程的意義在于一個應(yīng)用程序的多個邏輯單元可以并發(fā)地執(zhí)行。但是多線程并不意味著多個用戶進程在執(zhí)行,操作系統(tǒng)也不把每個線程作為獨立的進程來分配獨立的系統(tǒng)資源。進程可以創(chuàng)建其子進程,子進程與父進程擁有不同的可執(zhí)行代碼和數(shù)據(jù)內(nèi)存空間。而在用于代表應(yīng)用程序的進程中多個線程共享數(shù)據(jù)內(nèi)存空間,但保持每個線程擁有獨立的執(zhí)行堆棧和程序執(zhí)行上下文(Context)。

基于上述區(qū)別,線程也可以稱為輕型進程 (Light Weight Process,LWP)。不同線程間允許任務(wù)協(xié)作和數(shù)據(jù)交換,使得在計算機系統(tǒng)資源消耗等方面非常廉價。

線程需要操作系統(tǒng)的支持,不是所有類型的計算機都支持多線程應(yīng)用程序。Java程序設(shè)計語言將線程支持與語言運行環(huán)境結(jié)合在一起,提供了多任務(wù)并發(fā)執(zhí)行的能力。這就好比一個人在處理家務(wù)的過程中,將衣服放到洗衣機中自動洗滌后將大米放在電飯鍋里,然后開始做菜。等菜做好了,飯熟了同時衣服也洗好了。

需要注意的是:在應(yīng)用程序中使用多線程不會增加 CPU 的數(shù)據(jù)處理能力。只有在多CPU 的計算機或者在網(wǎng)絡(luò)計算體系結(jié)構(gòu)下,將Java程序劃分為多個并發(fā)執(zhí)行線程后,同時啟動多個線程運行,使不同的線程運行在基于不同處理器的Java虛擬機中,才能提高應(yīng)用程序的執(zhí)行效率。

另外,如果應(yīng)用程序必須等待網(wǎng)絡(luò)連接或數(shù)據(jù)庫連接等數(shù)據(jù)吞吐速度相對較慢的資源時,多線程應(yīng)用程序是非常有利的?;贗nternet的應(yīng)用程序有必要是多線程類型的,例如,當(dāng)開發(fā)要支持大量客戶機的服務(wù)器端應(yīng)用程序時,可以將應(yīng)用程序創(chuàng)建成多線程形式來響應(yīng)客戶端的連接請求,使每個連接用戶獨占一個客戶端連接線程。這樣,用戶感覺服務(wù)器只為連接用戶自己服務(wù),從而縮短了服務(wù)器的客戶端響應(yīng)時間。

Java語言的多線程程序設(shè)計方法



利用Java語言實現(xiàn)多線程應(yīng)用程序的方法很簡單。根據(jù)多線程應(yīng)用程序繼承或?qū)崿F(xiàn)對象的不同可以采用兩種方式:一種是應(yīng)用程序的并發(fā)運行對象直接繼承Java的線程類Thread;另外一種方式是定義并發(fā)執(zhí)行對象實現(xiàn)Runnable接口。

繼承Thread類的多線程程序設(shè)計方法

Thread 類是JDK中定義的用于控制線程對象的類,在該類中封裝了用于進行線程控制的方法。見下面的示例代碼:

//Consumer.java

import java.util.*;

class Consumer extends Thread

{

int nTime;

String strConsumer;

public Consumer(int nTime, String strConsumer)

{ this.nTime = nTime;

this.strConsumer = strConsumer;

}

public void run()

{

while(true)

{ try

{

System.out.println("Consumer name:"+strConsumer+" ");

Thread.sleep(nTime);

}

catch(Exception e)

{

e.printStackTrace();

}  } }

static public void main(String args[])

{ Consumer aConsumer = new Consumer (1000, "aConsumer");

aConsumer.start();

Consumer bConsumer = new Consumer (2000, "bConsumer");

bConsumer.start();

Consumer cConsumer = new Consumer (3000, "cConsumer ");

cConsumer.start();

} }

從上面的程序代碼可以看出:多線程執(zhí)行地下Consumer繼承Java語言中的線程類Thread并且在main方法中創(chuàng)建了三個Consumer對象的實例。當(dāng)調(diào)用對象實例的start方法時,自動調(diào)用Consumer類中定義的run方法啟動對象線程運行。線程運行的結(jié)果是每間隔nTime時間打印出對象實例中的字符串成員變量strConsumer的內(nèi)容。

可以總結(jié)出繼承Thread類的多線程程序設(shè)計方法是使應(yīng)用程序類繼承Thread類并且在該類的run方法中實現(xiàn)并發(fā)性處理過程。



實現(xiàn)Runnable接口的多線程程序設(shè)計方法



Java語言中提供的另外一種實現(xiàn)多線程應(yīng)用程序的方法是多線程對象實現(xiàn)Runnable接口并且在該類中定義用于啟動線程的run方法。這種定義方式的好處在于多線程應(yīng)用對象可以繼承其它對象而不是必須繼承Thread類,從而能夠增加類定義的邏輯性。

實現(xiàn)Runnable接口的多線程應(yīng)用程序框架代碼如下所示:

//Consumer.java

import java.util.*;

class Consumer implements Runnable

{

… …

public Consumer(int nTime, String strConsumer){… …}

public void run(){… …}

static public void main(String args[])

{

Thread aConsumer = new Thread(new Consumer(1000, "aConsumer"));

aConsumer.start();

//其它對象實例的運行線程

//… …

} }

從上述代碼可以看出:該類實現(xiàn)了Runnable接口并且在該類中定義了run方法。這種多線程應(yīng)用程序的實現(xiàn)方式與繼承Thread類的多線程應(yīng)用程序的重要區(qū)別在于啟動多線程對象的方法設(shè)計方法不同。在上述代碼中,通過創(chuàng)建Thread對象實例并且將應(yīng)用對象作為創(chuàng)建Thread類實例的參數(shù)。

線程間的同步

Java 應(yīng)用程序的多個線程共享同一進程的數(shù)據(jù)資源,多個用戶線程在并發(fā)運行過程中可能同時訪問具有敏感性的內(nèi)容。在Java中定義了線程同步的概念,實現(xiàn)對共享資源的一致性維護。下面以筆者最近開發(fā)的移動通信計費系統(tǒng)中線程間同步控制方法,說明Java語言中多線程同步方式的實現(xiàn)過程。

在沒有多線程同步控制策略條件下的客戶賬戶類定義框架代碼如下所示:

public class RegisterAccount

{float fBalance;

//客戶繳費方法

public void deposit(float fFees){ fBalance += fFees; }

//通話計費方法

public void withdraw(float fFees){ fBalance -= fFees; }

… …

}  

讀者也許會認(rèn)為:上述程序代碼完全能夠滿足計費系統(tǒng)實際的需要。確實,在單線程環(huán)境下該程序確實是可靠的。但是,多進程并發(fā)運行的情況是怎樣的呢?假設(shè)發(fā)生這種情況:客戶在客戶服務(wù)中心進行繳費的同時正在利用移動通信設(shè)備僅此通話,客戶通話結(jié)束時計費系統(tǒng)啟動計費進程,而同時服務(wù)中心的工作人員也提交繳費進程運行。讀者可以看到如果發(fā)生這種情況,對客戶賬戶的處理是不嚴(yán)肅的。

如何解決這種問題呢?很簡單,在 RegisterAccount類方法定義中加上用于標(biāo)識同步方法的關(guān)鍵字synchronized。這樣,在同步方法執(zhí)行過程中該方法涉及的共享資源(在上述代碼中為fBalance成員變量)將被加上共享鎖,以確保在方法運行期間只有該方法能夠?qū)蚕碣Y源進行訪問,直到該方法的線程運行結(jié)束打開共享鎖,其它線程才能夠訪問這些共享資源。在共享鎖沒有打開的時候其它訪問共享資源的線程處于阻塞狀態(tài)。

進行線程同步策略控制后的RegisterAccount類定義如下面代碼所示:

public class RegisterAccount

{float fBalance;

public synchronized void deposit(float fFees){ fBalance += fFees; }

public synchronized void withdraw(float fFees){ fBalance -= fFees; }

… …

}

從經(jīng)過線程同步機制定義后的代碼形式可以看出:在對共享資源進行訪問的方法訪問屬性關(guān)鍵字(public)后附加同步定義關(guān)鍵字synchronized,使得同步方法在對共享資源訪問的時候,為這些敏感資源附加共享鎖來控制方法執(zhí)行期間的資源獨占性,實現(xiàn)了應(yīng)用系統(tǒng)數(shù)據(jù)資源的一致性管理和維護。

線程的狀態(tài)控制



在這里需要明確的是:無論采用繼承Thread類還是實現(xiàn)Runnable接口來實現(xiàn)應(yīng)用程序的多線程能力,都需要在該類中定義用于完成實際功能的run方法,這個run方法稱為線程體(Thread Body)。按照線程體在計算機系統(tǒng)內(nèi)存中的狀態(tài)不同,可以將線程分為創(chuàng)建、就緒、運行、睡眠、掛起和死亡等類型。這些線程狀態(tài)類型下線程的特征為:

創(chuàng)建狀態(tài):當(dāng)利用new關(guān)鍵字創(chuàng)建線程對象實例后,它僅僅作為一個對象實例存在,JVM沒有為其分配CPU時間片等線程運行資源;

就緒狀態(tài):在處于創(chuàng)建狀態(tài)的線程中調(diào)用start方法將線程的狀態(tài)轉(zhuǎn)換為就緒狀態(tài)。這時,線程已經(jīng)得到除CPU時間之外的其它系統(tǒng)資源,只等JVM的線程調(diào)度器按照線程的優(yōu)先級對該線程進行調(diào)度,從而使該線程擁有能夠獲得CPU時間片的機會。

睡眠狀態(tài):在線程運行過程中可以調(diào)用sleep方法并在方法參數(shù)中指定線程的睡眠時間將線程狀態(tài)轉(zhuǎn)換為睡眠狀態(tài)。這時,該線程在不釋放占用資源的情況下停止運行指定的睡眠時間。時間到達后,線程重新由JVM線程調(diào)度器進行調(diào)度和管理。

掛起狀態(tài):可以通過調(diào)用suspend方法將線程的狀態(tài)轉(zhuǎn)換為掛起狀態(tài)。這時,線程將釋放占用的所有資源,由JVM調(diào)度轉(zhuǎn)入臨時存儲空間,直至應(yīng)用程序調(diào)用resume方法恢復(fù)線程運行。

死亡狀態(tài):當(dāng)線程體運行結(jié)束或者調(diào)用線程對象的stop方法后線程將終止運行,由JVM收回線程占用的資源。

在Java線程類中分別定義了相應(yīng)的方法,用于在應(yīng)用程序中對線程狀態(tài)進行控制和管理。

線程的調(diào)度

線程調(diào)用的意義在于JVM應(yīng)對運行的多個線程進行系統(tǒng)級的協(xié)調(diào),以避免多個線程爭用有限資源而導(dǎo)致應(yīng)用系統(tǒng)死機或者崩潰。

為了線程對于操作系統(tǒng)和用戶的重要性區(qū)分開,Java定義了線程的優(yōu)先級策略。Java將線程的優(yōu)先級分為10個等級,分別用1-10之間的數(shù)字表示。數(shù)字越大表明線程的級別越高。相應(yīng)地,在Thread類中定義了表示線程最低、最高和普通優(yōu)先級的成員變量MIN_PRIORITY、 MAX_PRIORITY和NORMAL_PRIORITY,代表的優(yōu)先級等級分別為1、10和5。當(dāng)一個線程對象被創(chuàng)建時,其默認(rèn)的線程優(yōu)先級是5。

為了控制線程的運行策略,Java定義了線程調(diào)度器來監(jiān)控系統(tǒng)中處于就緒狀態(tài)的所有線程。線程調(diào)度器按照線程的優(yōu)先級決定那個線程投入處理器運行。在多個線程處于就緒狀態(tài)的條件下,具有高優(yōu)先級的線程會在低優(yōu)先級線程之前得到執(zhí)行。線程調(diào)度器同樣采用"搶占式"策略來調(diào)度線程執(zhí)行,即當(dāng)前線程執(zhí)行過程中有較高優(yōu)先級的線程進入就緒狀態(tài),則高優(yōu)先級的線程立即被調(diào)度執(zhí)行。具有相同優(yōu)先級的所有線程采用輪轉(zhuǎn)的方式來共同分配CPU時間片。

在應(yīng)用程序中設(shè)置線程優(yōu)先級的方法很簡單,在創(chuàng)建線程對象之后可以調(diào)用線程對象的setPriority方法改變該線程的運行優(yōu)先級,同樣可以調(diào)用getPriority方法獲取當(dāng)前線程的優(yōu)先級。

在Java 中比較特殊的線程是被稱為守護(Daemon)線程的低級別線程。這個線程具有最低的優(yōu)先級,用于為系統(tǒng)中的其它對象和線程提供服務(wù)。將一個用戶線程設(shè)置為守護線程的方式是在線程對象創(chuàng)建之前調(diào)用線程對象的setDaemon方法。典型的守護線程例子是JVM中的系統(tǒng)資源自動回收線程,它始終在低級別的狀態(tài)中運行,用于實時監(jiān)控和管理系統(tǒng)中的可回收資源。

線程分組管理

Java定義了在多線程運行系統(tǒng)中的線程組(ThreadGroup)對象,用于實現(xiàn)按照特定功能對線程進行集中式分組管理。用戶創(chuàng)建的每個線程均屬于某線程組,這個線程組可以在線程創(chuàng)建時指定,也可以不指定線程組以使該線程處于默認(rèn)的線程組之中。但是,一旦線程加入某線程組,該線程就一直存在于該線程組中直至線程死亡,不能在中途改變線程所屬的線程組。

當(dāng)Java的Application應(yīng)用程序運行時,JVM創(chuàng)建名稱為main的線程組。除非單獨指定,在該應(yīng)用程序中創(chuàng)建的線程均屬于main線程組。在main線程組中可以創(chuàng)建其它名稱的線程組并將其它線程加入到該線程組中,依此類推,構(gòu)成線程和線程組之間的樹型管理和繼承關(guān)系。

與線程類似,可以針對線程組對象進行線程組的調(diào)度、狀態(tài)管理以及優(yōu)先級設(shè)置等。在對線程組進行管理過程中,加入到某線程組中的所有線程均被看作統(tǒng)一的對象。

小結(jié)

本文針對Java平臺中線程的性質(zhì)和應(yīng)用程序的多線程策略進行了分析和講解。與其它操作系統(tǒng)環(huán)境不同,Java運行環(huán)境中的線程類似于多用戶、多任務(wù)操作系統(tǒng)環(huán)境下的進程,但在進程和線程的運行及創(chuàng)建方式等方面,進程與Java線程具有明顯區(qū)別。

Unix 操作系統(tǒng)環(huán)境下,應(yīng)用程序可以利用fork函數(shù)創(chuàng)建子進程,但子進程與該應(yīng)用程序進程擁有獨立的地址空間、系統(tǒng)資源和代碼執(zhí)行單元,并且進程的調(diào)度是由操作系統(tǒng)來完成的,使得在應(yīng)用進程之間進行通信和線程協(xié)調(diào)相對復(fù)雜。而Java應(yīng)用程序中的多線程則是共享同一應(yīng)用系統(tǒng)資源的多個并行代碼執(zhí)行體,線程之間的通信和協(xié)調(diào)方法相對簡單。

可以說:Java語言對應(yīng)用程序多線程能力的支持增強了Java作為網(wǎng)絡(luò)程序設(shè)計語言的優(yōu)勢,為實現(xiàn)分布式應(yīng)用系統(tǒng)中多客戶端的并發(fā)訪問以及提高服務(wù)器的響應(yīng)效率奠定堅實基礎(chǔ)。
向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI