Java多线程开发系列之四:玩转多线程(线程的控制2)
? 在上節(jié)的線程控制(詳情點擊這里)中,我們講解了線程的等待join()、守護(hù)線程。本節(jié)我們將會把剩下的線程控制內(nèi)容一并講完,主要內(nèi)容有線程的睡眠、讓步、優(yōu)先級、掛起和恢復(fù)、停止等。
廢話不多說,我們直接進(jìn)入正題:
?3、線程睡眠? sleep()
所有介紹多線程開發(fā)的學(xué)習(xí)案例中,基本都有用到這個方法,這個方法的意思就是睡眠(是真的,請相信我...)。好吧,如果你覺得不夠具體,可以認(rèn)為是讓當(dāng)前線程暫停一下,當(dāng)前線程隨之進(jìn)入阻塞狀態(tài),當(dāng)睡眠時間結(jié)束后,當(dāng)前線程重新進(jìn)入就緒狀態(tài),開始新一輪的搶占計劃!
????? 那么這個方法在實際開發(fā)中,有哪些用途呢?我舉個例子,很多情況下,當(dāng)前線程并不需要實時的監(jiān)控或者是運(yùn)行,只是會定期的檢查一下某個狀態(tài)是否達(dá)標(biāo),如果符合出發(fā)條件了,那么就做某一件事情,否則繼續(xù)睡眠。比如心跳模式下,我們會派一個守護(hù)線程向服務(wù)端發(fā)送數(shù)據(jù)請求,當(dāng)收到回應(yīng)時,那么我們會睡眠一段時間,當(dāng)再次蘇醒后,我們繼續(xù)發(fā)送這樣的請求。現(xiàn)實生活中的例子,比如我們在等某個電視是否開播,可是又不想看之前的廣告,所以我們可能會等一會將電視頻道切換到要播放的位置查看一下,如果還在播放廣告,那么我們就跳到其他頻道觀看,然后定期的切換到目標(biāo)頻道進(jìn)行查看一下。
????? 代碼如下:
1 public class ThreadStudy 2 { 3 public static main(String[] arg)throws Exception 4 { 5 for(int i=0;i<=1000;i++) 6 { 7 if(IsInternetAccess()) 8 { 9 Thread.sleep(1000*6);//注意這里 10 } 11 else 12 { 13 System.out.println("Error! Can not Access Internet!") 14 break; 15 } 16 } 17 } 18 private Boolean IsInternetAccess() 19 { 20 //bala bala 21 return true; 22 } 23 }?
代碼的意思是檢查網(wǎng)絡(luò)是否通暢,如果通暢的話那么進(jìn)入睡眠,睡眠6秒鐘后再次蘇醒進(jìn)行一次檢查。通過讓線程睡眠,我們可以有效的分配資源,在閑時讓其他線程可以更快的拿到cpu資源。這里有一點需要注意的是,線程睡眠后,進(jìn)入阻塞狀態(tài)(無論此時cpu是否空閑,都仍然會暫停,是強(qiáng)制性的),當(dāng)睡眠時間結(jié)束,進(jìn)入的是就緒狀態(tài),需要再次競爭才可以搶占到cpu權(quán)限,而非睡眠結(jié)束后立即可以執(zhí)行方法。所以實際間隔時間是大于等于睡眠時間的。
????? java Thread類提供了兩個靜態(tài)方法來暫停線程
1 static void sleep(long millis) 2 3 static void sleep(long millis,int nanos)? millis為毫秒,nanos為微秒,與線程join()類似,由于jvm和硬件的緣故,我們也基本只用方法1。
4、?線程讓步 yield()
????? 在生活中我們都遇到過這樣的例子,在公交車、地鐵上作一名安靜的美男子(或者是女漢子),這時候進(jìn)來了一位老人、孕婦等,你默默的站起來,將座位讓給了老人。自己去旁邊候著,等著新的空閑座位。或者是你默默的玩著電腦游戲,然后你媽媽大聲的喊你的全名(是的,是全名),這時候你第一反應(yīng)是,我又做錯什么了,第二反應(yīng)就是放下手上的鼠標(biāo),乖乖的跑到你老媽面前接受訓(xùn)斥。所有的這一切都是由于事情的緊急性當(dāng)前正在處理的線程被擱置起來,我們(cpu)處理當(dāng)前的緊急事務(wù)。在軟件開發(fā)中,也有類似的場景,比如一條線程處理的任務(wù)過大,其他線程始終無法搶占到資源,這時候我們就要主動的進(jìn)行讓步,給其他線程一個公平搶占的機(jī)會。
????? 這里附加一份來自網(wǎng)絡(luò)的圖片:在我們強(qiáng)大的時候,我們應(yīng)該給弱者一個機(jī)會。咳咳? 回歸正題。
下面是代碼
?
1 public class TestThread extends Thead 2 { 3 public testThread(String name) 4 { 5 super(name); 6 } 7 8 public void run() 9 { 10 for(int i=0;i<=1000000;ii++) 11 { 12 send("MsgBody"); 13 if(i%100==0) 14 { 15 Thread.yield();//注意看這里 16 } 17 } 18 } 19 20 public static void main(String[] args) throws Exception 21 { 22 TestThread thread1=new TestThread("thread1"); 23 thread1.setPriority(Thread.MAX_PRIORITY);//注意看這里 24 25 TestThread thread2=new TestThread("thread2"); 26 thread1.setPriority(Thread.MIN_PRIORITY);//注意看這里 27 thread1.start(); 28 thread2.start(); 29 } 30 }我們啟動線程后,當(dāng)線程每發(fā)送一百次消息后,我們暫停一次當(dāng)前線程,使當(dāng)前線程進(jìn)入就緒狀態(tài)。此時CPU會重新計算一次優(yōu)先級,選擇優(yōu)先級較高者啟動。
此處比較一下 sleep方法和yield()方法。
(1)sleep方法 暫停線程后,線程會進(jìn)入阻塞狀態(tài)(即使是一瞬間),那么在這一刻cpu只會選擇已經(jīng)做好就緒狀態(tài)的線程,故不會選擇當(dāng)前正在睡眠的線程。(即使沒有其他可用線程)。而yield()方法會使當(dāng)前線程即刻起進(jìn)入就緒狀態(tài),cpu選擇的可選線程范圍中,包含當(dāng)前執(zhí)行yield()方法的線程。如若沒有其他線程的優(yōu)先級高于(或者等于) yield()的線程,則cpu仍會選擇原有yield()的線程重新啟動。
(2)sleep方法會拋出 InterruptedException 異常,所以調(diào)用sleep方法需要聲明或捕捉該異常(比C#處理異常而言是夠麻煩的),而yield沒有聲明拋出異常。
(3)sleep方法的移植性較好,可以對應(yīng)很多平臺的底層方法,所以用sleep()的地方要多余yield()的地方;
(4)sleep 暫停線程后,線程會睡眠 一定時間,然后才會變?yōu)榫途w狀態(tài),倘若定義為sleep(0)后,則阻塞狀態(tài)的時間為0,即刻進(jìn)入就緒狀態(tài),這種用法與yield()的用法基本上是相同的:即都是讓cpu進(jìn)行一次新的選擇,避免由于當(dāng)前線程過度的霸占cpu,造成程序假死。
這兩個方法最大的不同點是 sleep會拋出異常需要處理,yield()不會; 而且兩者的微小區(qū)別在各個版本的jdk中也不一樣,大家看以參閱stackoverflow上的這個問題:Are Thread.sleep(0) and Thread.yield() statements equivalent?(點此進(jìn)入)
5、線程的優(yōu)先級設(shè)定
線程的優(yōu)先級相當(dāng)于是一個機(jī)會的權(quán)重,優(yōu)先級高時,獲得執(zhí)行機(jī)會的可能性就越大,反之獲得執(zhí)行機(jī)會的可能性就越小。(記住只是可能性越大或越小)。
在本節(jié)的線程讓步這一部分的代碼里我們已經(jīng)用代碼展示了如何設(shè)置線程的優(yōu)先級此處不做特別的代碼展示。(防盜連接:本文首發(fā)自http://www.cnblogs.com/jilodream/ )
Thread為我們提供了兩個方法來分別設(shè)置和獲取線程的優(yōu)先級。
1 setPriority(int newPriority) 2 getPriority()
setPriority為設(shè)置優(yōu)先級,參數(shù)的取值范圍是 1~10之前。
同時還設(shè)定了三個靜態(tài)常量:
Tread.MAX_PRIORITY=10;
Tread.NORM_PRIORITY=5;
Tread.MIN_PRIORITY=1;
? 盡管java為線程提供了10個優(yōu)先級,但是底層平臺線程的優(yōu)先級往往并不為10,所以就導(dǎo)致了兩者不是意義對應(yīng)的關(guān)系。(比如OS只有五個優(yōu)先級,這樣每兩個優(yōu)先級只對應(yīng)一個OS的優(yōu)先級)。 此時我們常常只用這三個靜態(tài)常量來設(shè)置優(yōu)先級,而不是詳細(xì)的指明具體的優(yōu)先級值(因為可能多個優(yōu)先級對應(yīng)OS的某一個優(yōu)先級),造成不必要的麻煩。
另外每個線程默認(rèn)的優(yōu)先級都與創(chuàng)建他的父進(jìn)程的優(yōu)先級相同,在默認(rèn)情況下Main線程優(yōu)先級為普通,所以上述代碼創(chuàng)建的新線程默認(rèn)也為普通優(yōu)先級。
下面是優(yōu)先級概念的重點:
其實你設(shè)置的優(yōu)先級并不能真正代表該線程的或者啟動的優(yōu)先級,這只是OS啟動線程時計算優(yōu)先級的一個參考指標(biāo)。OS還會查看當(dāng)前線程是否長時間的霸占cpu,如果是這樣的話,OS會適度的調(diào)高對其它“饑餓”線程的優(yōu)先級。對于那些長期霸占cpu的線程進(jìn)行強(qiáng)制的掛起。進(jìn)行這種設(shè)置只是能在某種程度上增加該線程被執(zhí)行的機(jī)會。其實那些長期霸占cpu的線程也并非單次霸占的時間長,而是被連續(xù)選中的情況非常多,造成一種長期霸占的假象。
所以設(shè)置優(yōu)先級后,線程真正執(zhí)行的順序并不可以預(yù)測甚至可以說是有點混亂的。在明白了這點以后,我們在開發(fā)控制多線程,并不能完全的寄希望于通過簡單的設(shè)置優(yōu)先級來安排線程的執(zhí)行順序。
此處參考了兩篇文章,更多詳情請參考原文:
(1)Java多線程 -- 線程的優(yōu)先級(原文鏈接)
(2)Thread.sleep(0)的意義(原文鏈接)
?
6、強(qiáng)制結(jié)束線程Stop()
? ??? 有時我們會發(fā)現(xiàn)有些正在運(yùn)行的線程,已經(jīng)沒有必要繼續(xù)執(zhí)行下去了,但是距離多線程結(jié)束還有一段時間,這時我們就需要強(qiáng)制結(jié)束多線程。java曾經(jīng)提供過一個專門用于結(jié)束線程的方法Stop(),但是這個方法現(xiàn)在已經(jīng)被廢棄掉了,并不推薦開發(fā)者使用。
這是由于這個方法具有固有的不安全性。用Thread.stop 來結(jié)束線程,jvm會強(qiáng)制釋放它鎖定的所有對象。當(dāng)某一時刻對象的狀態(tài)并不一致時(正在處理事務(wù)的過程中),如果強(qiáng)制釋放掉對象,則可能會導(dǎo)致很多意想不到的后果。說的具體一點就是:系統(tǒng)會以被鎖定資源的棧頂產(chǎn)生一個ThreadDeath異常。這個unchecked Exception 會默默的關(guān)閉掉相關(guān)的線程。此時對象內(nèi)部的數(shù)據(jù)可能會不一致,而用戶并不會收到任何對象不一致的報警。這個不一致的后果只會在未來使用過程中才會被發(fā)現(xiàn),此時已經(jīng)造成了無法預(yù)料的后果。
有些人可能會考慮通過調(diào)用Stop方法,然后再捕捉ThreadDeath的形式,避免這種形式。這種想法看似可以實現(xiàn),其實由于ThreadDeath這個異常可能在任何位置拋出,需要及細(xì)致的考慮。而且即使考慮到了,在捕捉處理該異常時,系統(tǒng)可能又會拋出新的ThreadDeath。所以我們應(yīng)該在源頭上就扼殺掉這種方式,而不是通過不斷的打補(bǔ)丁來修復(fù)。
????? 那么問題來了,如果我們真的要關(guān)閉掉某個線程,應(yīng)該怎么處理呢?
????? 通過Stop方法的講解我們可以明白,在線程的外部來關(guān)閉線程往往很難處理好數(shù)據(jù)一致性、以及線程內(nèi)部運(yùn)行過程的問題。那么我們可以通過設(shè)定一直標(biāo)志變量,然后線程定期的檢查這個變量是否為結(jié)束標(biāo)識來確定是否繼續(xù)運(yùn)行。
????? 例如筆者曾經(jīng)寫過一個監(jiān)控計算機(jī)指標(biāo)的線程。這個線程會定期的檢查緩存中的狀態(tài)變量。這個狀態(tài)緩存是外部可以設(shè)定的。當(dāng)線程發(fā)現(xiàn)此變量已經(jīng)被設(shè)定為“結(jié)束”時,則會在內(nèi)部處理好剩余工作,直接運(yùn)行完Run方法。
?
7、線程的掛起和恢復(fù) suspend()和resume()
????? 我們有時需要對線程進(jìn)行掛起,而具體掛起的時間并不清楚,只可能在未來某個條件下,通知這個線程可以開始工作了。java為我們專門提供了這樣的兩個方法:
???? ?掛起 suspend()/恢復(fù)resume。
????? 通過標(biāo)題我們已經(jīng)知道這兩個方法也同樣不被java所推薦,但是為什么會這樣呢?
????? suspend是直接掛起當(dāng)前線程,使其進(jìn)入阻塞狀態(tài),而對他內(nèi)部控制和鎖定的資源并不進(jìn)行修改(這與stop方法類似,線程外部往往很難查看內(nèi)部運(yùn)行的狀態(tài)和控制的資源,所以也就很難處理)。這樣這個被掛起的線程所鎖定的資源就再也不能被其他資源所訪問,造成了一種假死鎖的狀態(tài)。只有當(dāng)線程被恢復(fù)(resume)后,并且釋放掉手里的資源,其他線程才可以重新訪問資源,但是倘若其他線程在恢復(fù)(resume)被掛起(suspend)的線程直線,需要先訪問被鎖定的資源,此時就會形成真正的鎖定。
????? 那么問題來了,如果我們真的要掛起某個線程,應(yīng)該怎么處理呢?
這個與stop()同理,我們可以在可能被掛起的線程內(nèi)部設(shè)置一個標(biāo)識,指出這個線程當(dāng)前是否要被掛起,若變量指示要掛起,則使用wait()命令讓其進(jìn)入等待狀態(tài),若標(biāo)識指出可以恢復(fù)線程時,則用notify()重新喚醒這個線程。(這兩個方法我會在后文的線程通信中講解)。???
????此處參考了兩篇文章,更多詳情請參考原文:
???? (1)為何不贊成使用Thread.stopsuspend和resume()(原文鏈接)
? (2)JAVA STOP方法的不安全性(原文鏈接)
轉(zhuǎn)載于:https://www.cnblogs.com/jilodream/p/4304439.html
總結(jié)
以上是生活随笔為你收集整理的Java多线程开发系列之四:玩转多线程(线程的控制2)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 字符串和数字之间的转化
- 下一篇: JAVA中几个修饰符的作用以及一些相关话