文章出處

線程的生命周期當線程被創建并啟動之后,它既不是一啟動就進入了執行狀態,也不是一直處于執行狀態,在線程的生命周期總,它要經過(新建)New、就緒(Runnable)、運行(Running)、阻塞(Blocked)和死亡(Dead)五種狀態。 當線程啟動之后,它不可能一直霸占著CPU獨自運行,所有CPU需要在多條線程之間切換,于是線程就也會多次在運行、就緒之間切換。 新建和就緒狀態當程序使用new關鍵字創建了一個線程之后,該線程就處于新建狀態,此時它和其它的JAVA對象一樣,僅由虛擬機為其分配內存,并初始化成員變量的值。此時的線程對象并沒有表現出任何線程的動態特征,程序也不會執行線程的線程執行體。 當線程對象調用了start()方法之后,該線程就處于就緒狀態,Java虛擬機會為其創建方法調用棧和程序計數器,處于該狀態的線程并沒有開始執行,只是表明該線程可以運行了,至于該線程何時執行,取決于JVM的調度。 啟動線程要調用start()方法而不是run()方法!永遠不要調用線程的run()方法,如果調用run()方法,系統會把線程對象當做普通的對象,會把線程執行體當做普通的方法來執行。 在調用run()方法之后,該線程就不在處于新建狀態,不要再調用該線程的start()方法。 Java中只能對處于新建狀態的線程使用start()方法,否則會引發IllegalThreadStateException異常。 如果希望調用子線程的start()方法后子線程立即開始執行,可以使用Thread.sleep(1)來讓 當前運行的線程(主線程)睡眠1毫秒,這樣CPU就會立刻執行另一個處于就緒狀態的線程。需要注意的是,使用Thread.Sleep()方法需要捕捉或聲明InterruptedException異常 運行和阻塞狀態當發生如下情況時,線程將會進入阻塞狀態: 線程調用sleep()方法主動放棄所占用的處理器資源線程調用了一個阻塞時IO方法,在該方法返回之前,該線程被阻塞線程試圖獲得一個同步監視器,但該同步監視器正被其他線程鎖持有線程正在等待某個通知(notify)程序調用了線程的suspend()方法將該線程掛起(該方法容易導致死鎖,盡量少用) 針對以上幾種情況,當發生如下情況時可以解除以上阻塞,讓該線程重新進入就緒狀態 調用sleep()方法過了指定時間線程調用的阻塞時IO方法依舊返回線程成功地獲得了試圖獲得的同步監視器現在正在等待某個通知,而其它線程發出一個通知處于掛起狀態的線程被調用了resume()方法 線程從阻塞狀態只能進入就緒狀態,無法直接進入運行狀態。就緒和運行狀態之間的轉換通常不受程序控制,而是由系統線程調度決定的。 調用yield()方法可以讓運行狀態的線程轉入就緒狀態。 當線程數大于處理器數是,依然存在多個線程在同一個CPU上輪換的現象。 線程死亡線程會以以下三種方式結束,結束后就處于死亡狀態: run()或call()方法執行完成,線程正常結束線程拋出一個未捕獲的Exception或Error直接調用該線程的stop()方法來結束該線程——該方法容易死鎖,通常不推薦使用  當主線程結束時,其它線程不受任何影響,并不會隨之結束。一旦子線程啟動起來后,它就擁有和主線程相同的地位,它不會受主線程影響。 為了測試某個線程是否已經死亡,可以調用該線程的isAlive()方法,當線程處于就緒、運行、阻塞三種狀態時,將返回true;當線程處于新建、死亡兩種狀態時,返回false。 不要試圖對一個已經死亡的線程調用start()方法讓它重新啟動,死亡后的線程不可以再作為線程運行。 如果對于非新建狀態的線程使用start方法,就會引發IllegalThreadStateException異常。  控制線程join線程Thread提供了讓一個線程等待另一個線程完成的方法——join()方法。當在某個程序執行流中調用其它線程的join()方法時,調用線程將被阻塞,直到被join()方法加入的join線程執行完畢位置。 join()方法通常有使用線程的程序調用,以將大問題劃分成許多小問題,每個小問題分配一個線程,當所有的小問題都得到處理之后,再調用主線程來進一步操作。

package 控制線程;public class JoinThread extends Thread{public JoinThread(String name){super(name);}public void run(){for(int i=0;i<10;i++){System.out.println(getName()+" "+i);}}public static void main(String []args)throws Exception{new JoinThread("新線程").start();for(int i=0;i<10;i++){if(i==5){JoinThread jt=new JoinThread("被join的線程");jt.start();jt.join();}System.out.println(Thread.currentThread().getName()+"  "+i);}}}

輸出結果為:\ 在“被join的線程”執行時,實際上只有2個子線程并發執行,而主線程處于等待狀態,知道“被join的線程”執行完畢,主線程會才再度執行。  后臺線程有一種線程,是在后臺運行的,其任務是為其它線程提供服務,這種線程被稱為“后臺線程”(Daemon Thread),又稱為“守護線程”或“精靈線程”。JVM的垃圾回收器就是典型的后臺進程。 當前臺進程全部死亡,后臺進程會自動死亡。調用Thread的setDaemon(true)方法可以把指定線程設置為后臺線程。 當整個虛擬機中只剩下后臺線程時,程序就沒有再運行的必要了,所以虛擬機將退出。 Thread類還提供了一個isDaemon()方法,用于判斷指定線程是否為后臺線程。 前臺線程創建的線程默認為前臺線程,后臺線程創建的線程默認為后臺線程。 前臺線程死亡后,JVM會通知后臺線程死亡,但從它接受指令到做出響應需要的一段時間。此外,要將某個線程設置為后臺線程,必須在該線程啟動之前設置,也就是說,setDaemon(true)必須在start()方法之前調用,否則會引發IllegalThreadStateException異常 線程睡眠:sleep 當前線程調用sleep()方法進入阻塞狀態后,在其睡眠時間內,該線程不會獲得執行的機會。 即使系統中沒有其它可執行的線程,處于sleep()中的線程也不會執行,因此sleep()方法常用來暫停程序的執行。 線程讓步:yieldyield會讓該線程暫停,但是它不會阻塞該線程,其只是將該線程轉入就緒狀態。也就是說,yield()方法只是讓當前線程暫停一下,讓系統的縣城調度器重新調度一次,完全可能出現這種情況——某個線程調用了yield()方法暫停之后,線程調度器又將其調度出來重新執行。 在多CPU并行的環境下,yield()方法的功能有時并不明顯 sleep()方法和yield()方法的區別:sleep()方法暫停當前線程后會給其它線程以執行機會,不會理會其它線程的優先級;但yield方法之后給優先級相同,或優先級更高的線程執行機會。sleep()方法會將線程轉入阻塞狀態,知道經過阻塞時間才會轉入就緒狀態;而yield()方法不會轉入阻塞狀態,其只是強制當前線程進入就緒狀態。sleep()方法聲明拋出了InterruptedException異常,所以調用sleep()方法要么捕獲該異常,要么顯示聲明拋出該異常;yield()方法則沒有聲明拋出任何異常。sleep()方法比yield()具有更好的可以執行,通常不建議使用yield()方法來控制并發線程的執行。  改變線程優先級每個線程都具有一定的優先級,優先級高的線程獲得較多的執行機會,而優先級低的線程獲得較少的執行機會。 每個線程默認的優先級都與創建它的父進程的優先級相同,默認情況下,main進程具有普通優先級。 Thread類提供的setThread(int newPriority)、getPriority()方法來設置和返回指定線程的優先級。其中setPriority方法的參數可以是一個整數,范圍是1-10之間。 Thread類中有以下三個靜態常量:MAX_PRIORITY :10 MIN_PRIORITY :1 NORM_PRIORITY:5 由于某些系統的優先級不能很好地與Java的10個優先級對應,所以使用上述靜態常量具有更好的移植性。 線程同步為解決線程安全問題,java提出了多種解決辦法同步代碼塊

synchronize(obj){...}
上面語法格式中的synchronized后括號里的obj就是同步監視器,上面代碼的含義是:線程開始執行同步代碼塊之前,必須現貨的對同步監聽器的鎖定。

 任何時刻只能有一個線程可以獲得對同步監視器的鎖定,當同步代碼塊完成執行后,該線程會釋放對該同步監視器的鎖定。 雖然Java程序允許使用任何對象作為同步監聽器,但通常推薦使用可能被并發訪問的共享資源充當同步監視器。 流程為:加鎖-->修改-->釋放鎖 同步方法與同步代碼塊,Java的多線程安全支持還提供了同步方法,同步方法就是使用某個synchronized關鍵字修飾某個方法,則該方法被稱為同步方法。 對于被synchronized修飾的實例方法(非static方法)而言,無需顯式指定同步監視器,同步方法的同步監視器是this,也就是調用該方法的對象。synchronized關鍵字可以修飾方法,可以修飾代碼塊,但不能修飾構造器、成員變量。 線程安全的類具有如下特征:該類的對象可以被多個線程安全地訪問每個線程調用該對象的任一方法之后都將得到正確的結果每個線程調用該對象的任一方法之后,該對象狀態依然保持合理狀態 Account.java

package 線程同步;public class Account {private String accountNo;private double balance;public Account(){}public Account(String AccountNo,double balance){this.accountNo=AccountNo;this.balance=balance;}public void setAccount(String account){accountNo=account;}public String getAccount(){return accountNo;}public double getBalance(){return balance;}public synchronized void draw(double drawAccount){if(balance>=drawAccount){System.out.println(Thread.currentThread().getName()+"   取款成功,取出現金  "+drawAccount+"元");try{Thread.sleep(1);}catch(InterruptedException ie){ie.printStackTrace();}balance-=drawAccount;System.out.println("\t余額為  "+balance);}else{System.out.println(Thread.currentThread().getName()+" 余額不足,取款失敗");}}public int hashCode(){return accountNo.hashCode();}public boolean equals(Object obj){if(this==obj)return true;if(obj!=null&&obj.getClass()==Account.class){Account a=(Account)obj;return a.getAccount().equals(accountNo);}return false;}}

ThreadTest.java

package 線程同步;public class ThreadTest extends Thread{private Account account;private double drawAccount;public ThreadTest(String name,Account account,double drawAccount){super(name);this.account=account;this.drawAccount=drawAccount;}public void run(){account.draw(drawAccount);}public static void main(String []args){Account account=new Account("3242332",1000);ThreadTest t1=new ThreadTest("甲",account,600);ThreadTest t2=new ThreadTest("乙",account,600);t1.start();t2.start();}}

運行結果為:\  可變類的線程安全是以降低運行效率為代價的,為減少線程安全鎖帶來的負面影響,可采用以下策略:不要對線程安全類中的所有方法都同步,只對那些會改變競爭資源(競爭資源也就是共享資源)的方法進行同步。如果可變類有單線程和多線程兩種運行環境,那么要為該可變類提供兩種版本(線程安全版和線程不安全版) StringBuffer和StringBuilder就是這種情況,在單線程時應使用StringBuilder,多線程時使用StringBuffer。
釋放同步監視器的鎖定線程會在以下幾種情況下釋放對同步監聽器的鎖定:當前線程的同步方法、同步代碼塊執行結束,當前線程釋放了同步監聽器。當前線程在同步代碼塊、同步方法中遇到了break、continue,終止了該代碼塊、方法的運行,當前線程釋放了同步監聽器。當前線程在同步代碼塊、同步方法中遇到了未處理的error、exception,導致該代碼塊、方法意外結束,當前線程釋放了同步監聽器。當前線程執行同步代碼塊或同步方法時,程序執行了同步監聽器對象的wait()方法,則當前線程暫停,并釋放同步監聽器。 在如下所示的情況下,線程不會釋放同步監聽器:線程執行同步代碼塊或同步方法時,程序調用Thread.sleep()、Thread.yield()方法來暫停當前線程的執行,當前線程不會釋放同步監視器。線程執行同步代碼塊時,其它線程調用了該線程的suspend()方法將該方法掛起,該線程不會釋放同步監聽器。(所有程序應該避免使用suspend和resume來操控線程)  同步鎖(Lock)從Java5開始,Java提供了一種功能更強大的線程同步機制——通過顯式定義同步鎖對象來實現同步,在這種機制下,同步鎖有Lock對象充當。 Lock提供了比synchronized方法和synchronized代碼塊更廣泛的鎖定操作,Lock允許實現更靈活的結構,可以具有很大的差別的屬性,并支持多個相關的Condition對象。 鎖提供了對共享資源的獨占訪問,每次只有一個線程對Lock對象加鎖,線程開始訪問共享資源之前應先獲得Lock對象。 某些鎖可能允許對共享資源并發訪問,如ReadWriteLock(讀寫鎖),Lock、ReadWriteLock是JAVA5提供的兩個根接口,并未Lock提供了ReetrantLock(可重入鎖)的實現類,為ReadWriteLock提供了ReentrantReadWriteLock實現類。 JAVA8新增了新型的StrampedLock類,在大多數場景下它可以替代傳統的ReentrantReadWriteLock。ReentrantReadWriteLock提供了三種鎖模式——Writing,ReadingOptimistic,Reading。 在實現線程安全的控制中,比較常用的ReentrantLock。使用該對象可以顯式的加鎖、釋放鎖。 使用ReentrantLock對象進行同步,加鎖和釋放鎖出現在不同的作用范圍時,通常建議使用finally塊確保在必要時釋放鎖。  ReentrantLock鎖具有可重入性,也就是說一個線程可以對已被加鎖的ReentrantLock再次加鎖,ReentrantLock對象會維持一個計數器來追蹤lock()方法的嵌套調用,線程在每次調用lock()加鎖后,必須顯式的地調用unlock()來釋放鎖,所以一段被鎖的代碼可以調用另一個被相同鎖保護的方法。  個人理解,所謂鎖,就是指在鎖的范圍內必須一次性執行,不能中途掛起并執行其它線程。 死鎖當兩線程相互等待對方釋放同步監視器是就會發生死鎖,JAVA虛擬機沒有監測、處理死鎖的措施,所以一定要避免死鎖的出現。 一旦出現死鎖,整個程序不會出現任何異常,也不會給出任何提示,只是所有線程處于阻塞狀態,無法繼續。 但是死鎖很容易發生,尤其是系統中出現多個同步監聽器的情況下。 由于Thread類的suspend()方法也很容易導致死鎖,所有JAVA不再推薦使用該方法來暫停線程的執行。 P740就愛閱讀www.92to.com網友整理上傳,為您提供最全的知識大全,期待您的分享,轉載請注明出處。
歡迎轉載:http://www.kanwencang.com/bangong/20161206/63637.html

文章列表




Avast logo

Avast 防毒軟體已檢查此封電子郵件的病毒。
www.avast.com


arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()