摘要:多線程編程是現(xiàn)代軟件技術(shù)中很重要的一個環(huán)節(jié)。要弄懂多線程,這就要牽涉到多進(jìn)程。本文主要以多線程編程以及多線程編程相關(guān)知識而做出的一些結(jié)論。
什么是多線程編程?
多線程編程技術(shù)是Java語言的重要特點。多線程編程的含義是將程序任務(wù)分成幾個并行的子任務(wù)。特別是在網(wǎng)絡(luò)編程中,你會發(fā)現(xiàn)很多功能是可以并發(fā)執(zhí)行的。比如網(wǎng)絡(luò)傳輸速度較慢、用戶輸入速度較慢,你可以用兩個獨立的線程去完成這兩個功能,而不影響正常的顯示或其它功能。
多線程是與單線程比較而言的,普通的Windows采用單線程程序結(jié)構(gòu),其工作原理是:主程序有一個消息循環(huán),不斷從消息隊列中讀入消息來決定下一步所要干的事情,一般是針對一個函數(shù),只有等這個函數(shù)執(zhí)行完之后,主程序才能接收另外的消息來執(zhí)行。比如子函數(shù)功能是在讀一個網(wǎng)絡(luò)數(shù)據(jù),或讀一個文件,只有等讀完這個數(shù)據(jù)或文件才能接收下一個消息。在執(zhí)行這個子函數(shù)過程中你什么也不能干。但往往讀網(wǎng)絡(luò)數(shù)據(jù)和等待用戶輸入有很多時間處于等待狀態(tài),多線程利用這個特點將任務(wù)分成多個并發(fā)任務(wù)后,就可以解決這個問題。
多線程編程基礎(chǔ)知識詳解
一、進(jìn)程
1.程序:由源代碼生成的可執(zhí)行應(yīng)用。(如:QQ.app)
2.進(jìn)程:一個正在運(yùn)行的程序可以看做一個進(jìn)程。(正在運(yùn)行的QQ就是一個進(jìn)程),進(jìn)程擁有獨立運(yùn)行所需的全部資源。
3.線程:程序中獨立運(yùn)行的代碼段。(如:接收QQ消息的代碼塊)
一個進(jìn)程是由一個和多個線程組成,進(jìn)程只負(fù)責(zé)資源的調(diào)度和分配,線程才是程序真正的執(zhí)行單元,負(fù)責(zé)代碼的執(zhí)行。
二、單線程
1、每個正在運(yùn)行的程序(即進(jìn)程),至少包含一個線程,這個線程叫主線程。
2、主線程在程序啟動時被創(chuàng)建,用于執(zhí)行main函數(shù)。
3、只有一個主線程的程序,稱作單線程程序。
4、主線程負(fù)責(zé)執(zhí)行程序所有的代碼(UI展現(xiàn)以及刷新,網(wǎng)絡(luò)請求,本地請求)。這些代碼只能順序執(zhí)行,無法并發(fā)執(zhí)行。
注意:UI展現(xiàn)和刷新只能寫在主線程中。
三、多線程
1、擁有多個線程的程序,稱作多線程程序。
2、iOS允許用戶自己開辟新的線程,相對于主線程來說,這些線程,稱作子線程。
3、子線程和主線程都是獨立運(yùn)行的單元,各自的執(zhí)行互不影響,因此能夠并發(fā)執(zhí)行。
補(bǔ)充的多線程的基本知識
4、iOS默認(rèn)給主線程分配1M的??臻g,默認(rèn)給子線程分配512K的棧空間。(分配的字節(jié)數(shù)必須是4K的整數(shù)倍)
5、分配的空間用來存放線程中為變量開辟的空間,一般情況下足夠使用。
6、與棧空間的使用方式不同,主線程和子線程共用同一塊內(nèi)存空間。
7、程序入口處默認(rèn)設(shè)置了自動釋放池,由主線程負(fù)責(zé)執(zhí)行代碼。子線程新開辟的內(nèi)存不在主線程管轄范圍內(nèi)(線程之間互不干擾,相互獨立),所以子線程為對象開辟的空間不會自動釋放,要手動為子線程添加自動釋放池。
8、短時間內(nèi)使用靜態(tài)方法開辟大量內(nèi)存空間或使用循環(huán)使用便利構(gòu)造器,會造成內(nèi)存瞬間集聚,程序carsh掉,所以要寫@autorelease pool。
9、多線程的種類
脫離線程:線程結(jié)束后被銷毀,子線程可能是脫離線程。
四、單、多線程的區(qū)別
單線程程序:只有一個線程,代碼順序執(zhí)行,容易出現(xiàn)代碼阻塞(頁面假死)
多線程程序:有多個線程,線程之間獨立運(yùn)行,能有效的避免代碼阻塞,并且提高程序的運(yùn)行性能。
五、JVM與多線程
Java編寫的程序都運(yùn)行在Java虛擬機(jī)(JVM)中,在JVM的內(nèi)部,程序的多任務(wù)是通過線程來實現(xiàn)的。
每用java命令啟動一個java應(yīng)用程序,就會啟動一個JVM進(jìn)程。在同一個JVM進(jìn)程中,有且只有一個進(jìn)程,就是它自己。在這個JVM環(huán)境中,所有程 序代碼的運(yùn)行都是以線程來運(yùn)行的。JVM找到程序程序的入口點main(),然后運(yùn)行main()方法,這樣就產(chǎn)生了一個線程,這個線程稱之為主線程。當(dāng) main方法結(jié)束后,主線程運(yùn)行完成。JVM進(jìn)程也隨即退出。
操作系統(tǒng)將進(jìn)程線程進(jìn)行管理,輪流(沒有固定的順序)分配每個進(jìn)程很短的一段時間(不一定是均分),然后在每個進(jìn)程內(nèi)部,程序代碼自己處理該進(jìn)程內(nèi)部線程的時間分配,多個線程之間相互的切換去執(zhí)行,這個切換時間也是非常短的。
六、Java語言對多線程的支持
Java語言對多線程的支持通過類Thread和接口Runnable來實現(xiàn)。這里就不多說了。這里重點強(qiáng)調(diào)兩個地方:
// 主線程其它代碼段
ThreadClass subThread = new ThreadClass(); subThread.start(); // 主線程其它代碼段 subThread.sleep(1000);
非脫離線程:線程結(jié)束后被掛起,等待喚醒,不銷毀。主線程一定是非脫離線程。
10、iOS中實現(xiàn)多線程的方法
a、NSObject
b、NSThread
c、NSOperation和NSOperationQueue結(jié)合使用
d、GCD(最重要的)
有人認(rèn)為以下的代碼在調(diào)用start()方法后,肯定是先啟動子線程,然后主線程繼續(xù)執(zhí)行。在調(diào)用sleep()方法后CPU什么都不做,就在那里等待休眠的時間結(jié)束。實際上這種理解是錯誤的。因為:
?、賡tart()方法的調(diào)用后并不是立即執(zhí)行多線程代碼,而是使得該線程變?yōu)榭蛇\(yùn)行態(tài)(Runnable),什么時候運(yùn)行是由操作系統(tǒng)決定的。
?、赥hread.sleep()方法調(diào)用目的是不讓當(dāng)前線程獨自霸占該進(jìn)程所獲取的CPU資源,以留出一定時間給其他線程執(zhí)行的機(jī)會(也就是靠內(nèi)部自己協(xié)調(diào))。
七、線程的狀態(tài)切換
1、新建狀態(tài)(New):新創(chuàng)建了一個線程對象。
2、就緒狀態(tài)(Runnable):線程對象創(chuàng)建后,其他線程調(diào)用了該對象的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)是線程因為某種原因放棄CPU使用權(quán),暫時停止運(yùn)行。直到線程進(jìn)入就緒狀態(tài),才有機(jī)會轉(zhuǎn)到運(yùn)行狀態(tài)。阻塞的情況分三種:
?、俚却枞哼\(yùn)行的線程執(zhí)行wait()方法,JVM會把該線程放入等待池中。 ②同步阻塞:運(yùn)行的線程在獲取對象的同步鎖時,若該同步鎖被別的線程占用,則JVM把該線程放入鎖池③其他阻塞:運(yùn)行的線程執(zhí)行sleep()或 join()方法,或者發(fā)出了I/O請求時,JVM會把該線程置為阻塞狀態(tài)。當(dāng)sleep()狀態(tài)超時、join()等待線程終止或者超時、或者I/O處 理完畢時,線程重新轉(zhuǎn)入就緒狀態(tài)。
5、死亡狀態(tài)(Dead):線程執(zhí)行完了或者因異常退出了run()方法,該線程結(jié)束生命周期。
八、為什么要多線程編程
為什么要多線程編程呢?這其中的原因很多,我們可以舉例解決
1)有的是為了提高運(yùn)行的速度,比如多核cpu下的多線程
2)有的是為了提高資源的利用率,比如在網(wǎng)絡(luò)環(huán)境下下載資源時,時延常常很高,我們可以通過不同的thread從不同的地方獲取資源,這樣可以提高效率
3)有的為了提供更好的服務(wù),比如說是服務(wù)器
4)其他需要多線程編程的地方等等
九、Java的線程模型
由于Java是純面向?qū)ο笳Z言,因此,Java的線程模型也是面向?qū)ο蟮摹ava通過Thread類將線程所必須的功能都封裝了起來。要想建立一個線 程,必須要有一個線程執(zhí)行函數(shù),這個線程執(zhí)行函數(shù)對應(yīng)Thread類的run方法。Thread類還有一個start方法,這個方法負(fù)責(zé)建立線程,相當(dāng)于 調(diào)用Windows的 建立線程函數(shù)CreateThread.當(dāng)調(diào)用start方法后,如果線程建立成功,并自動調(diào)用Thread類的run方法。因此,任何繼承Thread 的Java類都可以通過Thread類的start方法來建立線程。如果想運(yùn)行自己的線程執(zhí)行函數(shù),那就要覆蓋Thread類的run方法。
在Java的線程模型中除了Thread類,還有一個標(biāo)識某個Java類是否可作為線程類的接口Runnable,這個接口只有一個抽象方法run,也就 是Java線程模型的線程執(zhí)行函數(shù)。因此,一個線程類的唯一標(biāo)準(zhǔn)就是這個類是否實現(xiàn)了Runnable接口的run方法,也就是說,擁有線程執(zhí)行函數(shù)的類 就是線程類。 從上面可以看出,在Java中建立線程有兩種方法,一種是繼承Thread類,另一種是實現(xiàn)Runnable接口,并通過Thread和實現(xiàn) Runnable的類來建立線程,其實這兩種方法從本質(zhì)上說是一種方法,即都是通過Thread類來建立線程,并運(yùn)行run方法的。但它們的大區(qū)別是通過 繼承Thread類來建立線程,雖然在實現(xiàn)起來更容易,但由于Java不支持多繼承,因此,這個線程類如果繼承了Thread,就不能再繼承其他的類了, 因此,Java線程模型提供了通過實現(xiàn)Runnable接口的方法來建立線程,這樣線程類可以在必要的時候繼承和業(yè)務(wù)有關(guān)的類,而不是Thread類。
評論