繼承Thread類建立線程:java
/** * 主類 */ public class ThreadTest { public static void main(String[] args) { //建立線程對象 My_Thread my_thread = new My_Thread(); //啓動線程 my_thread.start(); } } /** * 繼承Thread */ class My_Thread extends Thread{ @Override public void run(){ //線程的任務 System.out.println("My_Thread Running"); } }
直接使用Thread類建立線程:windows
class ThreadTest02 { public static void main(String[] args) { //直接使用Thread建立線程,"My_Thread"是取得線程名 Thread my_thread = new Thread("My_Thread"){ @Override public void run() { //線程的任務 System.out.println("My_Thread Running"); } }; //啓動線程 my_thread.start(); } }
以上的方式都是直接使用Thread類建立線程,並經過start方法啓動線程,但線程並不會當即執行,它還須要等待CPU調度,只有線程得到CPU控制權,纔算是真正在執行。ide
直接使用Thread類的好處是:
方便傳參,可在子類裏添加成員變量,經過set方式設置參數或經過構造函數傳參函數
直接使用Thread類的缺點處是:
線程的建立和任務代碼冗餘在一塊兒。也可能因爲繼承了Thread類,故沒法再繼承其餘類。任務無返回值。操作系統
/** * 主類 */ public class ThreadTest03 { public static void main(String[] args) { RunnableTask task = new RunnableTask(); //建立線程,參數1 是任務對象; 參數2 是線程名字,推薦寫上 Thread my_thread = new Thread(task,"My_Thread"); //啓動線程 my_thread.start(); } } /** * Runable接口實現類 */ class RunnableTask implements Runnable{ @Override public void run(){ //線程的任務 System.out.println("Thread Running"); } }
以上的方式是使用Runnable接口的run方法,該方式將任務代碼與線程的建立分離,這樣在多個線程具備相同任務時,就可使用同一個Runnable接口實現,同時該方式的Runnable的實現類也能夠繼承其餘的類。該方式更靈活,故推薦使用其來建立線程。線程
但其缺點也是任務無返回值。code
//建立任務類,相似於Runnable public class CallerTask implements Callable<String> { @Override public String call() throws Exception { return "hello thread"; } public static void main(String[] args) throws ExecutionException, InterruptedException { //建立任務對象 FutureTask<String> futureTask = new FutureTask<>(new CallerTask()); //啓動線程 new Thread(futureTask,"My_Thread").start(); //主線程等待"My_Thread"的任務執行完畢,並返回結果 String res = futureTask.get(); System.out.println(res); } }
上述代碼實現了Callable接口的call()方法。在main函數內首先建立FutureTask對象(構造函數爲CallerTask的實例)。將建立的FutureTask對象做爲任務,並放到新建立的線程中啓動。運行完畢後,則可使用get方法等待線程裏的任務執行完畢並返回結果。對象
Java線程在其生命週期中可能有六種狀態。根據Java.lang.Thread類中的枚舉類型State的定義,其狀態有如下六種:blog
①NEW:初始狀態,線程已被建立但還未調用start()方法來進行啓動。繼承
②RUNNABLE:運行狀態,調用start方法後,線程處於該狀態。注意,Java線程的運行狀態,實際上包含了操做系統中的就緒狀態(已得到除CPU外的一切運行資源,正在等待CPU調度,得到CPU控制權)和運行狀態(得到CPU控制權,線程真正在執行)。所以,即便Java中的線程處於RUNNABLE狀態,也並不意味着該線程就必定正在執行(得到CPU的控制權),該線程也有可能在等待CPU調度。
③BLOCKED:阻塞狀態,線程阻塞於鎖,即線程在鎖的競爭中失敗,則處於阻塞狀態。
④WAITING:等待狀態,該狀態的線程須要等待其餘線程的中斷或通知。
⑤TIME-WAITING:超時等待狀態,該狀態下的線程也在等待通知,但若在限定時間內沒有,其餘線程進行通知,那麼超過規定時間的線程就會自動「醒來」,繼續執行run方法內的代碼。
⑥TERMINATED:終止狀態,線程執行完畢或者線程在執行過程當中拋出異常,則線程結束,線程處於終止狀態。
阻塞狀態(BLOCKED),是由於其在鎖競爭中失敗而在等待得到鎖,而等待狀態(WAITING)則是在等待某一事件的發生,常見的如等待其餘線程的通知或者中斷。
(1)、start方法
是否爲static方法:否。
做用:啓動一個新線程,在新線程調用run方法。
說明:線程調用start方法,進入運行狀態(RUNNABLE),但並不意味着線程中的代碼會當即執行,由於Java線程中的運行狀態包含了操做系統層面的【就緒狀態】和【運行狀態】,因此只有Java線程真正得到了CPU的控制權,線程才能真正地在執行。每一個線程只能調用一次start方法來啓動線程,若是屢次調用則會出現IllegalThreadStateException。
(2)、run方法
是否爲static方法:否。
做用:線程啓動後會調用的方法。
說明:
①若使用繼承Thread類的方式建立線程,並重寫了run方法,則線程會在啓動後調用run方法,執行其中的代碼。若是繼承時沒有重寫run方法或者run方法中沒有任何代碼,則該線程不會進行任何操做。
②若使用實現Runnable接口的方法建立線程,則在調用start啓動線程後,也會調用Runnable實現類中的run方法,若是沒有重寫,則默認不會進行任何操做。
那些run方法和start方法又有什麼區別呢?
③start方法是真正能啓動一個新線程的方法,而run方法則是線程對象中的普通方法,即便線程沒有啓動,也能夠經過線程對象來調用run方法,run方法並不會啓動一個新線程。
代碼以下:
public class StartAndRun{ public static void main(String[] args) { //使用Thread建立線程 Thread t = new Thread("my_thread"){ //爲線程命名爲"my_thread" @Override public void run() { //Thread.currentThread().getName():獲取當前線程的名字 System.out.println("【"+Thread.currentThread().getName()+"】"+"線程中的run方法被調用"); for (int i = 0; i < 3; i++) { System.out.println(i); } } }; //調用run方法 t.run(); //調用start方法 t.start(); } }
其結果以下:
【main】線程中的run方法被調用 0 1 2 【my_thread】線程中的run方法被調用 0 1 2
能夠看出在my_thread線程啓動前(調用start方法前),也能夠調用線程對象t中的run方法,調用這個run方法的線程並不會是my_thread線程(由於還沒啓動呢),而是main方法所在的主線程main。這是由於run方法是做爲線程對象的普通方法存在的,能夠認爲run方法中的代碼就是新線程啓動後所須要執行的任務。若是經過線程對象調用run方法,那麼在哪一個線程調用的run方法,就由哪一個線程負責執行。
總的來講,Thread類的對象實例對應着操做系統實際存在的一個線程,該對象實例負責提供給用戶去操做線程、獲取線程信息。start方法會調用native修飾的本地方法start0,最終在操做系統中啓動一個線程,並會在本地方法中調用線程對象實例的run方法。因此,調用run方法並不會啓動一個線程,它只是做爲線程對象等着被調用。
(3)、join方法
是否爲static方法:否。
做用:用於同步,可使用該方法讓線程之間的並行執行變爲串行執行。
有代碼以下:
/** * 主類 */ public class Join { public static void main(String[] args) throws InterruptedException { Task task = new Task(); Thread t1 = new Thread(task,"耗子尾汁"); //啓動線程 t1.start(); //主線程打印 for(int i = 0; i < 4; i++){ if (i == 2) { //join方法:使main線程與t1線程同步執行,即t1線程執行完,main線程纔會繼續 t1.join(); } //Thread.currentThread().getName():獲取當前線程的名稱 System.out.println("【"+Thread.currentThread().getName()+"】" + i); } } } /** * Runnable接口實現類 */ class Task implements Runnable{ @Override public void run() { for(int i = 0; i < 3; i++){ System.out.println("【"+Thread.currentThread().getName()+"】"+i); } } }
其輸出以下:
【main】0 【main】1 【耗子尾汁】0 【耗子尾汁】1 【耗子尾汁】2 【耗子尾汁】3 【main】2 【main】3
在上面的代碼中,建立了一個命名爲「耗子尾汁」的線程,並經過start方法啓動。主線程和「耗子尾汁」線程都有循環打印i的任務。在「耗子尾汁」線程啓動後,就會進入運行狀態(Runnable),等待CPU調度,以得到CPU使用權來打印i。而主線程在執行「耗子尾汁」線程的start方法後,就會繼續往下執行,循環打印i。正常來說,主線程和「耗子尾汁」線程應該處於並行執行的狀態,即兩者會各自執行本身的for循環。但因爲在主線程的for循環中調用了join方法,使得主線程交出了CPU的控制權,並返回到「耗子尾汁」線程,等待該線程執行完畢,主線程才繼續執行。因此join方法就至關於在主線程中同步「耗子尾汁」線程,使「耗子尾汁」線程執行完,纔會繼續執行主線程。其最終效果就是可使用該方法讓線程之間的並行執行變爲串行執行。
join方法是能夠傳參的。join(10)的意思就是,若是在A線程中調用了B線程.join(10),那麼A線程就會同步等待B線程10毫秒,10毫秒後,A、B線程就會並行執行。
同時也要注意,只有線程啓動了,調用join方法纔有意義。在上述代碼中,若是「耗子尾汁」線程沒有調用start方法來啓動,那麼join並不會起做用。
(4)、getId方法、getName方法、setName方法
是否爲static方法:均爲否。
做用:
①getId方法:獲取線程長整型的id、這個線程id是惟一的。
②getName方法:獲取線程名
③setName(String):設置線程名
(5)、getPriority方法、setPriority(int)方法
是否爲static方法:均爲否。
做用:
①setPriority(int)方法:設置線程的優先級,優先級的範圍爲1-10。
②getPriority方法:獲取線程的優先級。
如今的主流操做系統(windows、Linux等)基本都採用了時分的形式來調度運行線程,即將CPU的時間分爲一個個時間片(這些時間片相等的),線程會獲得若干時間片,時間片用完就會發生線程調度,並等待下一次的分配。線程優先級就是決定線程須要多或者少分配一些時間片。
Java線程的優先級範圍爲1-10,默認優先級爲5。優先級高的線程分配的時間片的數量要都多於優先級低的線程。可經過setPriority(int)方法來設置。頻繁阻塞的線程(好比I/O操做或休眠)的線程須要設置較高優先級,而計算任務較重(好比偏向運算操做或須要較多CPU時間)的線程則設置較低優先級,以免CPU會被獨佔。
須要注意的是,Java線程的優先級設置只能給操做系統建議,並不能直接決定線程的調度,Java線程的調度只能由操做系統決定。操做系統徹底能夠忽略Java線程的優先級設置。在不一樣的操做系統上Java線程的優先級會存在差別,一些操做系統會直接無視優先級的設置。因此一些在邏輯上有前後順序的操做,不能依靠設置Java線程的優先級來完成。
Java子線程的默認優先級與父線程的優先級一致,例如在main方法中建立線程,那麼主線程(默認爲5)就是這個新線程的父線程,該新線程的默認優先級爲父線程的優先級。若是給主線程設置優先級爲4,那麼這個新線程的默認優先級就爲4。
(6)、getState()方法、isAlive()方法
是否爲static方法:均爲否。
做用:
①getState()方法:獲取線程的狀態(NEW、RUNNABLE、WATING、BLOCKED、TIME_WATING、TERMINATED)
②isAlive()方法:判斷線程是否存活,便是否線程已啓動但還沒有終止((尚未運行完
畢))。
(7)、interrupt()方法
是否爲static方法:否。
做用:中斷線程,當A線程運行時,B線程能夠經過A線程的對象實例來調用A線程的interrput()方法設置線程A的中斷標誌位true,並當即返回。設置中斷僅僅是設置標誌,經過設置中斷標誌並不能直接終止該線程的執行,而是被中斷的線程根據中斷狀態自行處理。若是打斷的是正在運行中的線程,那麼該線程就會被設置中斷標誌。但若是線程正在執行sleep方法或者上面所說的join方法時,被調用了interrupt方法,那麼這個被打斷的線程會拋出出 InterruptedException異常,並清除打斷標誌。
(8)、interrupted()方法、isInterrupted()方法
是否爲static方法:interrupted爲非static方法、isInterrupted爲static方法
做用:均爲判斷線程是否被打斷。區別在於interrupted()方法不會清除中斷標記,isInterrupted()方法會清除中斷標誌。
(9)、sleep(long n)方法
是否爲static方法:是。
做用:讓線程休眠,當一個執行中的線程調用sleep方法後,該線程就會掛起,並把剩下的CPU時間片交給其餘線程,但並不會直接指定由哪一個線程佔用,須要操做系統來進行調度。線程在休眠期間不參與CPU調度,但也不會把線程佔有的其餘資源(好比鎖)進行釋放。
須要注意的是,休眠時間到後線程也並不會直接繼續執行,而是進入等待CPU調度的狀態。同時因爲sleep方法是靜態方法,使用t.sleep()並不會讓t線程進入休眠,而是讓當前線程進入休眠(好比在main方法中調用t.sleep(),其實是讓主線程進入休眠)。
(10)、yield() 方法 是否爲static方法:是。 做用:使線程讓出CPU控制權。實際上該方法只是向操做系統請求讓出本身的CPU控制權,但操做系統也能夠選擇忽略。線程調用該方法讓出CPU控制權後,會進入就緒狀態,也有可能遇到剛讓出CPU控制權後又被CPU調度執行的狀況。