大師,最近我在學(xué)習線程,有很多迷惑的地方。
說來聽聽,讓為師給你排解一下。
第一個問題問題就是為什么要多線程啊, 我看了操作系統(tǒng)中的多進程管理,不是挺好的嗎? 多線程似乎沒有必要?。?/p>
不錯,多問問為什么,總是有好處的。所謂線程,就是程序代碼的執(zhí)行,一個進程至少得有一個線程,要不然,這個進程怎么運行? 對吧?
這個我理解。
以你常用的Word為例,假設(shè)這個進程沒有多線程(或者說它只有一個線程), 如果它有個定時保存文檔的功能,你想象下,當這個自動保存的功能在運行的時候,你還能繼續(xù)輸入文字嗎?
不能
對,這就是問題所在了,單線程只能干一件事情,無法并發(fā)和并行。直接導(dǎo)致用戶體驗不好。CPU快速的運算能力,還有多核,就被浪費了。
那我完全可以用多進程來處理啊, 一個進程來接受用戶輸入文字,另外一個進程來自動保存。
是可以這么做,但進程是個重量級的家伙,并且進程之間是隔離的,他們要想共享數(shù)據(jù),例如被編輯的文件內(nèi)容,那是非常麻煩的。
原來如此。
古人云:進程是擁有資源的基本單位, 線程是CPU調(diào)度的基本單位,這句話你理解嗎?
不是特別明白......
舉個例子,有兩個進程,一個是Word, 一個是QQ音樂。 Word 進程打開了文件,這是它的資源,QQ音樂打開了Socket,這也是它的資源。
假設(shè)Word有兩個線程:T1負責接受用戶的文字輸入,T2 負責自動保存。
QQ音樂也有兩個線程:T3負責從Socket中讀取數(shù)據(jù),T4負責對音樂數(shù)據(jù)進行解碼。
操作系統(tǒng)在做調(diào)度的時候,基本單位不是Word,QQ音樂這樣的進程,而是T1, T2,T3,T4這些線程。明白了嗎?
(點頭)原來每個線程執(zhí)行的都是進程代碼的某個片段??! 對了,我聽師兄們在討論什么Java多線程編程,說是很復(fù)雜,但是他們從來沒提到過Java 多進程編程,這是怎么回事???
不錯,你的師兄們都沒有想到這個問題,看來你已經(jīng)開始思考了。我先問你,你寫的Java程序是不是運行在JVM中? 對操作系統(tǒng)來說,JVM是個什么東西?
嗯... JVM其實就是java.exe運行起來,那它肯定是個進程了。
那在一個進程中還能進行多進程編程嗎?
(恍然大悟)奧,那是肯定不行了,Java程序運行在JVM當中, JVM這個進程其實就是他們的容器。 我聽說Python, Ruby 等動態(tài)語言也都有虛擬機, 這么說他們也可以進行多線程編程了。
是啊,虛擬機是個好東西,你們真是遇上了好時候啊, 不用再費勁心機去操作內(nèi)存。這虛擬機還能屏蔽操作系統(tǒng)的差異,你寫的程序可以在任意的支持該語言虛擬機的操作系統(tǒng)中運行。 可移植性很重要,要不然,你在Mac/Windows上開發(fā)的程序怎么能不加修改地放到Linux上去運行呢?
我在Java 中創(chuàng)建了一個Thread對象,為什么要調(diào)用start方法才能啟動線程? 為什么不能直接調(diào)用run方法呢?
你要是直接調(diào)用run()方法,會是什么效果?
就是用當前線程去執(zhí)行一個普通函數(shù)而已,根本沒有什么新線程創(chuàng)建出來。
這就對了,你想創(chuàng)建一個新的線程出來,肯定得有準備工作啊,設(shè)置好這個線程的上下文,比如這個線程的棧(用于函數(shù)調(diào)用),線程的狀態(tài),這個線程的PC(Program Counter)等等一系列信息以后,這個線程才可以被調(diào)度, 一旦被調(diào)度,就會執(zhí)行那個run()方法了
明白了大師,還有一個問題,既然線程是屬于進程的,可以共享進程的資源, 那創(chuàng)建一個線程應(yīng)該很輕松啊,為什么要有線程池這個東西呢?
雖然線程是個輕量級的東西, 但是對于互聯(lián)網(wǎng)應(yīng)用來說,如果每個用戶的請求都創(chuàng)建一個線程,那會非常得多,服務(wù)器也是難于承受, 再說了,眾多的線程去競爭CPU,不斷切換,也會讓CPU調(diào)度不堪重負,很多線程將不得不等待。所以前輩們的思路就是(1)用少量的線程 (2) 讓線程保持忙碌
奧,就是說只創(chuàng)建一定數(shù)量的線程,讓這些線程去處理所有的任務(wù),任務(wù)執(zhí)行完了以后,線程并不結(jié)束,而是回到線程池中去,等待接受下一個任務(wù)。
這些線程可以預(yù)先創(chuàng)建,任務(wù)來了就不用臨時再創(chuàng)建了,立刻開始服務(wù)。
預(yù)先創(chuàng)建? 您剛才不是說線程是程序代碼的執(zhí)行嗎? 它是個動態(tài)的東西,怎么可能預(yù)先創(chuàng)建? 如果真的創(chuàng)建起來了,就會調(diào)用run方法, 馬上執(zhí)行完了, 線程就結(jié)束了!
你忘了重要的一點,線程的狀態(tài)。當線程池的線程剛創(chuàng)建時,讓他們進入阻塞狀態(tài):等待某個任務(wù)的到來。 如果任務(wù)來了,那就好辦,喚醒其中一個線程,讓它拿到任務(wù)去執(zhí)行即可。
可是怎么讓他們進入阻塞狀態(tài)?
看來之前的圖我白畫了, BlockingQueue聽說過沒有? 沒聽說過? 其實很簡單,就是一個線程調(diào)用它的take()方法取數(shù)據(jù)時, 如果這個Queue中沒有數(shù)據(jù),該線程會阻塞;同樣,一個線程調(diào)用它的put方法放數(shù)據(jù)時,如果Queue滿了, 也會阻塞。
奧,看來線程池中每個線程的run()方法中,要設(shè)置一個循環(huán),每次都嘗試從BlockingQueue中獲取任務(wù),如果Queue是空的,就阻塞等待, 如果有任務(wù)來了,就會通知到線程池的某一個線程去處理,處理完了以后,依然試圖從BlockingQueue中獲取任務(wù),就這么依次循環(huán)下去。
線程池中的Worker線程:public class WorkerThread extends Thread { private BlockingQueue
沒錯,你這個代碼是一種簡單的實現(xiàn),我的老朋友Doug Lea大師寫了一套非常好的實現(xiàn),已經(jīng)被吸收進JDK了,作為java.util.concurrent包的一部分,你直接調(diào)用即可,不用自己動手了。
ExecutorService executorService = Executors.newFixedThreadPool(10);executorService.execute(new Runnable() { public void run() { System.out.println("Asynchronous task"); }});executorService.shutdown();
-
線程池
+關(guān)注
關(guān)注
0文章
57瀏覽量
6828 -
線程
+關(guān)注
關(guān)注
0文章
504瀏覽量
19636
原文標題:小白科普:線程和線程池
文章出處:【微信號:LinuxDev,微信公眾號:Linux閱碼場】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論