多線程是什麼?為什麼要用多線程?
介紹多線程之前要介紹線程,介紹線程則離不開程序。
首先 程序 :是一個正在執行中的程式,每一個程序執行都有一個執行順序,該順序是一個執行路徑,或者叫一個控制單元;
線程:就是程序中的一個獨立控制單元,線程在控制着程序的執行。一個程序中至少有一個程序。
多線程:一個程序中不隻有一個線程。
為什麼要用多線程:
①、為了更好的利用cpu的資源,如果隻有一個線程,則第二個任務必須等到第一個任務結束後才能進行,如果使用多線程則在主線程執行任務的同時可以執行其他任務,而不需要等待;
②、程序之間不能共享資料,線程可以;
③、系統建立程序需要為該程序重新配置設定系統資源,建立線程代價比較小;
④、Java語言内置了多線程功能支援,簡化了java多線程程式設計。
二、線程的生命周期:
建立 :從建立一個線程對象到程式start() 這個線程之間的狀态,都是建立狀态;
就緒 :線程對象調用start()方法後,就處于就緒狀态,等到JVM裡的線程排程器的排程;
運作 :就緒狀态下的線程在擷取CPU資源後就可以執行run(),此時的線程便處于運作狀态,運作狀态的線程可變為就緒、阻塞及死亡三種狀态。
等待/阻塞/睡眠 :在一個線程執行了sleep(睡眠)、suspend(挂起)等方法後會失去所占有的資源,進而進入阻塞狀态,在睡眠結束後可重新進入就緒狀态。
終止 :run()方法完成後或發生其他終止條件時就會切換到終止狀态。
三、建立線程的方法:具體實作代碼詳解請看點選後方連結:多線程擴充一、建立線程的三種方法詳細對比(http://www.cnblogs.com/yjboke/p/8919090.html)
1、繼承Thread類:
步驟:①、定義類繼承Thread;
②、複寫Thread類中的run方法;
目的:将自定義代碼存儲在run方法,讓線程運作
③、調用線程的start方法:
該方法有兩步:啟動線程,調用run方法。
2、實作Runnable接口: 接口應該由那些打算通過某一線程執行其執行個體的類來實作。類必須定義一個稱為run 的無參方法。
實作步驟: ①、定義類實作Runnable接口
②、覆寫Runnable接口中的run方法
将線程要運作的代碼放在該run方法中。
③、通過Thread類建立線程對象。
④、将Runnable接口的子類對象作為實際參數傳遞給Thread類的構造函數。
自定義的run方法所屬的對象是Runnable接口的子類對象。是以要讓線程執行指定對象的run方法就要先明确run方法所屬對象
⑤、調用Thread類的start方法開啟線程并調用Runnable接口子類的run方法。
3、通過Callable和Future建立線程:
實作步驟:①、建立Callable接口的實作類,并實作call()方法,改方法将作為線程執行體,且具有傳回值。
②、建立Callable實作類的執行個體,使用FutrueTask類進行包裝Callable對象,FutureTask對象封裝了Callable對象的call()方法的傳回值
③、使用FutureTask對象作為Thread對象啟動新線程。
④、調用FutureTask對象的get()方法擷取子線程執行結束後的傳回值。
四、繼承Thread類和實作Runnable接口、實作Callable接口的差別。
繼承Thread:線程代碼存放在Thread子類run方法中。
優勢:編寫簡單,可直接用this.getname()擷取目前線程,不必使用Thread.currentThread()方法。
劣勢:已經繼承了Thread類,無法再繼承其他類。
實作Runnable:線程代碼存放在接口的子類的run方法中。
優勢:避免了單繼承的局限性、多個線程可以共享一個target對象,非常适合多線程處理同一份資源的情形。
劣勢:比較複雜、通路線程必須使用Thread.currentThread()方法、無傳回值。
實作Callable:
優勢:有傳回值、避免了單繼承的局限性、多個線程可以共享一個target對象,非常适合多線程處理同一份資源的情形。
劣勢:比較複雜、通路線程必須使用Thread.currentThread()方法
建議使用實作接口的方式建立多線程。
五、線程狀态管理
1、線程睡眠---sleep:
線程睡眠的原因:線程執行的太快,或需要強制執行到下一個線程。
線程睡眠的方法(兩個):sleep(long millis)在指定的毫秒數内讓正在執行的線程休眠。
sleep(long millis,int nanos)在指定的毫秒數加指定的納秒數内讓正在執行的線程休眠。
線程睡眠的代碼示範:
public class SynTest { public static void main(String[] args) { new Thread(new CountDown(),"倒計時").start(); } }class CountDown implements Runnable{ int time = 10; public void run() { while (true) { if(time>=0){ System.out.println(Thread.currentThread().getName() + ":" + time--); try { Thread.sleep(1000); //睡眠時間為1秒 } catch (InterruptedException e) { e.printStackTrace(); } } } } }
每隔一秒則會列印一次,列印結果為:
倒計時:10倒計時:9倒計時:8倒計時:7倒計時:6倒計時:5倒計時:4倒計時:3倒計時:2倒計時:1倒計時:0
擴充:Java線程排程是Java多線程的核心,隻有良好的排程,才能充分發揮系統的性能,提高程式的執行效率。但是不管程式員怎麼編寫排程,隻能最大限度的影響線程執行的次序,而不能做到精準控制。因為使用sleep方法之後,線程是進入阻塞狀态的,隻有當睡眠的時間結束,才會重新進入到就緒狀态,而就緒狀态進入到運作狀态,是由系統控制的,我們不可能精準的去幹涉它,是以如果調用Thread.sleep(1000)使得線程睡眠1秒,可能結果會大于1秒。
2、線程讓步---yield:
該方法和sleep方法類似,也是Thread類提供的一個靜态方法,可以讓正在執行的線程暫停,但是不會進入阻塞狀态,而是直接進入就緒狀态。相當于隻是将目前線程暫停一下,然後重新進入就緒的線程池中,讓線程排程器重新排程一次。也會出現某個線程調用yield方法後暫停,但之後排程器又将其排程出來重新進入到運作狀态。
public class SynTest { public static void main(String[] args) { yieldDemo ms = new yieldDemo(); Thread t1 = new Thread(ms,"張三吃完還剩"); Thread t2 = new Thread(ms,"李四吃完還剩"); Thread t3 = new Thread(ms,"王五吃完還剩"); t1.start(); t2.start(); t3.start(); } }class yieldDemo implements Runnable{ int count = 20; public void run() { while (true) { if(count>0){ System.out.println(Thread.currentThread().getName() + count-- + "個瓜"); if(count % 2 == 0){ Thread.yield(); //線程讓步 } } } } }
sleep和yield的差別:
①、sleep方法聲明抛出InterruptedException,調用該方法需要捕獲該異常。yield沒有聲明異常,也無需捕獲。
②、sleep方法暫停目前線程後,會進入阻塞狀态,隻有當睡眠時間到了,才會轉入就緒狀态。而yield方法調用後 ,是直接進入就緒狀态。
3、線程合并---join:
當B線程執行到了A線程的.join()方法時,B線程就會等待,等A線程都執行完畢,B線程才會執行。
join可以用來臨時加入線程執行。
以下為代碼示範:
public static void main(String[] args) throws InterruptedException { yieldDemo ms = new yieldDemo(); Thread t1 = new Thread(ms,"張三吃完還剩"); Thread t2 = new Thread(ms,"李四吃完還剩"); Thread t3 = new Thread(ms,"王五吃完還剩"); t1.start(); t1.join(); t2.start(); t3.start(); System.out.println( "主線程"); }
4、停止線程:
原stop方法因有缺陷已經停用了,那麼現在改如何停止線程?現在分享一種,就是讓run方法結束。
開啟多線程運作,運作的代碼通常是循環結構,隻要控制住循環,就可以讓run方法結束