點我跳過黑哥的卑鄙廣告行爲,進入正文。html
Java多線程系列更新中~java
正式篇:算法
番外篇(神TM番外篇):多線程
咱們可能常常會用到 Thread.Sleep 函數來使線程掛起一段時間。那麼你有沒有正確的理解這個函數的用法呢?思考下面這兩個問題:函數
咱們先回顧一下操做系統原理。spa
操做系統中,CPU競爭有不少種策略。Unix系統使用的是時間片算法,而Windows則屬於搶佔式的。操作系統
在時間片算法中,全部的進程排成一個隊列。操做系統按照他們的順序,給每一個進程分配一段時間,即該進程容許運行的時間。若是在 時間片結束時進程還在運行,則CPU將被剝奪並分配給另外一個進程。若是進程在時間片結束前阻塞或結束,則CPU立即進行切換。調度程 序所要作的就是維護一張就緒進程列表,,當進程用完它的時間片後,它被移到隊列的末尾。.net
所謂搶佔式操做系統,就是說若是一個進程獲得了 CPU 時間,除非它本身放棄使用 CPU ,不然將徹底霸佔 CPU 。所以能夠看出,在搶 佔式操做系統中,操做系統假設全部的進程都是「人品很好」的,會主動退出 CPU 。線程
在搶佔式操做系統中,假設有若干進程,操做系統會根據他們的優先級、飢餓時間(已經多長時間沒有使用過 CPU 了),給他們算出一 個總的優先級來。操做系統就會把 CPU 交給總優先級最高的這個進程。當進程執行完畢或者本身主動掛起後,操做系統就會從新計算一 次全部進程的總優先級,而後再挑一個優先級最高的把 CPU 控制權交給他。htm
咱們用分蛋糕的場景來描述這兩種算法。假設有源源不斷的蛋糕(源源不斷的時間),一副刀叉(一個CPU),10個等待吃蛋糕的人(10 個進程)。
若是是 Unix操做系統來負責分蛋糕,那麼他會這樣定規矩:每一個人上來吃 1 分鐘,時間到了換下一個。最後一我的吃完了就再從頭開始。因而,無論這10我的是否是優先級不一樣、飢餓程度不一樣、飯量不一樣,每一個人上來的時候均可以吃 1 分鐘。固然,若是有人原本不太餓,或者飯量小,吃了30秒鐘以後就吃飽了,那麼他能夠跟操做系統說:我已經吃飽了(掛起)。因而操做系統就會讓下一我的接着來。
若是是 Windows 操做系統來負責分蛋糕的,那麼場面就頗有意思了。他會這樣定規矩:我會根據大家的優先級、飢餓程度去給大家每一個人計算一個優先級。優先級最高的那我的,能夠上來吃蛋糕——吃到你不想吃爲止。等這我的吃完了,我再從新根據優先級、飢餓程度來計算每一個人的優先級,而後再分給優先級最高的那我的。
這樣看來,這個場面就有意思了——可能有些人是PPMM,所以具備高優先級,因而她就能夠常常來吃蛋糕。可能另一我的是個醜男,而去很ws,因此優先級特別低,因而好半天了才輪到他一次(由於隨着時間的推移,他會愈來愈飢餓,所以算出來的總優先級就會愈來愈高,所以總有一天會輪到他的)。並且,若是一不當心讓一個大胖子獲得了刀叉,由於他飯量大,可能他會霸佔着蛋糕連續吃好久好久,致使旁邊的人在那裏咽口水。。。
並且,還可能會有這種狀況出現:操做系統如今計算出來的結果,5號PPMM總優先級最高,並且高出別人一大截。所以就叫5號來吃蛋糕。5號吃了一小會兒,以爲沒那麼餓了,因而說「我不吃了」(掛起)。所以操做系統就會從新計算全部人的優先級。由於5號剛剛吃過,所以她的飢餓程度變小了,因而總優先級變小了;而其餘人由於多等了一下子,飢餓程度都變大了,因此總優先級也變大了。不過這時候仍然有可能5號的優先級比別的都高,只不過如今只比其餘的高一點點——但她仍然是總優先級最高的啊。所以操做系統就會說:5號mm上來吃蛋糕……(5號mm內心鬱悶,這不剛吃過嘛……人家要減肥……誰叫你長那麼漂亮,得到了那麼高的優先級)。
那麼,Thread.Sleep 函數是幹嘛的呢?還用剛纔的分蛋糕的場景來描述。上面的場景裏面,5號MM在吃了一次蛋糕以後,以爲已經有8分飽了,她以爲在將來的半個小時以內都不想再來吃蛋糕了,那麼她就會跟操做系統說:在將來的半個小時以內不要再叫我上來吃蛋糕了。這樣,操做系統在隨後的半個小時裏面從新計算全部人總優先級的時候,就會忽略5號mm。Sleep函數就是幹這事的,他告訴操做系統「在將來的多少毫秒內我不參與CPU競爭」。
看完了 Thread.Sleep 的做用,咱們再來想一想文章開頭的兩個問題。
對於第一個問題,答案是:不必定。由於你只是告訴操做系統:在將來的1000毫秒內我不想再參與到CPU競爭。那麼1000毫秒過去以後,這時候也許另一個線程正在使用CPU,那麼這時候操做系統是不會從新分配CPU的,直到那個線程掛起或結束;何況,即便這個時候恰巧輪到操做系統進行CPU 分配,那麼當前線程也不必定就是總優先級最高的那個,CPU仍是可能被其餘線程搶佔去。
與此類似的,Thread有個Resume函數,是用來喚醒掛起的線程的。好像上面所說的同樣,這個函數只是「告訴操做系統我從如今起開始參與CPU競爭了」,這個函數的調用並不能立刻使得這個線程得到CPU控制權。
對於第二個問題,答案是:有,並且區別很明顯。假設咱們剛纔的分蛋糕場景裏面,有另一個PPMM 7號,她的優先級也很是很是高(由於很是很是漂亮),因此操做系統老是會叫道她來吃蛋糕。並且,7號也很是喜歡吃蛋糕,並且飯量也很大。不過,7號人品很好,她很善良,她沒吃幾口就會想:若是如今有別人比我更須要吃蛋糕,那麼我就讓給他。所以,她能夠每吃幾口就跟操做系統說:咱們來從新計算一下全部人的總優先級吧。不過,操做系統不接受這個建議——由於操做系統不提供這個接口。因而7號mm就換了個說法:「在將來的0毫秒以內不要再叫我上來吃蛋糕了」。這個指令操做系統是接受的,因而此時操做系統就會從新計算你們的總優先級——注意這個時候是連7號一塊兒計算的,由於「0毫秒已通過去了」嘛。所以若是沒有比7號更須要吃蛋糕的人出現,那麼下一次7號仍是會被叫上來吃蛋糕。
所以,Thread.Sleep(0)的做用,就是「觸發操做系統馬上從新進行一次CPU競爭」。競爭的結果也許是當前線程仍然得到CPU控制權,也許會換成別的線程得到CPU控制權。這也是咱們在大循環裏面常常會寫一句Thread.Sleep(0) ,由於這樣就給了其餘線程好比Paint線程得到CPU控制權的權力,這樣界面就不會假死在那裏。
末了說明一下,雖然上面提到說「除非它本身放棄使用 CPU ,不然將徹底霸佔 CPU」,但這個行爲仍然是受到制約的——操做系統會監控你霸佔CPU的狀況,若是發現某個線程長時間霸佔CPU,會強制使這個線程掛起,所以在實際上不會出現「一個線程一直霸佔着 CPU 不放」的狀況。至於咱們的大循環形成程序假死,並非由於這個線程一直在霸佔着CPU。實際上在這段時間操做系統已經進行過屢次CPU競爭了,只不過其餘線程在得到CPU控制權以後很短期內立刻就退出了,因而就又輪到了這個線程繼續執行循環,因而就又用了好久才被操做系統強制掛起。。。所以反應到界面上,看起來就好像這個線程一直在霸佔着CPU同樣。
末了再說明一下,文中線程、進程有點混亂,其實在Windows原理層面,CPU競爭都是線程級的,本文中把這裏的進程、線程當作同一個東西就行了。
文章轉載於https://blog.csdn.net/u012660667/article/details/49102107,可是他也是轉載的,並且沒聲明出處,這裏就寫他的文章地址吧