建立線程的兩種方法:面試
一、繼承Thread類,並重寫Thread類的run方法編程
二、實現接口Runnable的run方法。多線程
注意:另外還有一種建立線程的方法筆者沒有指出,使用線程池方式—Callable接口。Callable接口實現類,call方法可拋出異常、返回線程任務執行完畢後的結果。jvm
面試問題:Thread類直接調用run()和調用start()方法的區別是什麼呢?ide
答:Thread的start()方法才能建立一個新的線程。若是直接調用run()方法,至關於直接調用一個類中的方法,不會建立一個新的線程。函數
1.5 建立線程方式一繼承Thread類
建立線程的步驟:
1 定義一個類繼承Thread。
2 重寫run方法。
3 建立子類對象,就是建立線程對象。
4 調用start方法,開啓線程並讓線程執行,同時還會告訴jvm去調用run方法。
測試類測試
1 public class Demo01 { 2 public static void main(String[] args) { 3 //建立自定義線程對象 4 MyThread mt = new MyThread("新的線程!"); 5 //開啓新線程 6 mt.start(); 7 //在主方法中執行for循環 8 for (int i = 0; i < 10; i++) { 9 System.out.println("main線程!"+i); 10 } 11 } 12 }
自定義線程類spa
public class MyThread extends Thread { //定義指定線程名稱的構造方法 public MyThread(String name) { //調用父類的String參數的構造方法,指定線程的名稱 super(name); } /** * 重寫run方法,完成該線程執行的邏輯 */ @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println(getName()+":正在執行!"+i); } } }
思考:線程對象調用 run方法和調用start方法區別?
線程對象調用run方法不開啓線程。僅是對象調用方法。線程對象調用start開啓線程,並讓jvm調用run方法在開啓的線程中執行。
1.5.1 繼承Thread類原理
咱們爲何要繼承Thread類,並調用其的start方法才能開啓線程呢?
繼承Thread類:由於Thread類用來描述線程,具有線程應該有功能。那爲何不直接建立Thread類的對象呢?以下代碼:
Thread t1 = new Thread();
t1.start();//這樣作沒有錯,可是該start調用的是Thread類中的run方法,而這個run方法沒有作什麼事情,更重要的是這個run方法中並無定義咱們須要讓線程執行的代碼。線程
建立線程的目的是什麼?
是爲了創建程序單獨的執行路徑,讓多部分代碼實現同時執行。也就是說線程建立並執行須要給定線程要執行的任務。
對於以前所講的主線程,它的任務定義在main函數中。自定義線程須要執行的任務都定義在run方法中。
Thread類run方法中的任務並非咱們所須要的,只有重寫這個run方法。既然Thread類已經定義了線程任務的編寫位置(run方法),那麼只要在編寫位置(run方法)中定義任務代碼便可。因此進行了重寫run方法動做。
1.5.2 多線程的內存圖解
多線程執行時,到底在內存中是如何運行的呢?
以上個程序爲例,進行圖解說明:
多線程執行時,在棧內存中,其實每個執行線程都有一片本身所屬的棧內存空間。進行方法的壓棧和彈棧。
當執行線程的任務結束了,線程自動在棧內存中釋放了。可是當全部的執行線程都結束了,那麼進程就結束了。
1.5.3 獲取線程名稱
開啓的線程都會有本身的獨立運行棧內存,那麼這些運行的線程的名字是什麼呢?該如何獲取呢?既然是線程的名字,按照面向對象的特色,是哪一個對象的屬性和誰的功能,那麼咱們就去找那個對象就能夠了。查閱Thread類的API文檔發現有個方法是獲取當前正在運行的線程對象。還有個方法是獲取當前線程對象的名稱。既然找到了,咱們就能夠試試。
Thread.currentThread()獲取當前線程對象
Thread.currentThread().getName();獲取當前線程對象的名稱code
1 class MyThread extends Thread { //繼承Thread 2 MyThread(String name){ 3 super(name); 4 } 5 //複寫其中的run方法 6 public void run(){ 7 for (int i=1;i<=20 ;i++ ){ 8 System.out.println(Thread.currentThread().getName()+",i="+i); 9 } 10 } 11 } 12 class ThreadDemo { 13 public static void main(String[] args) { 14 //建立兩個線程任務 15 MyThread d = new MyThread(); 16 MyThread d2 = new MyThread(); 17 d.run();//沒有開啓新線程, 在主線程調用run方法 18 d2.start();//開啓一個新線程,新線程調用run方法 19 } 20 }
經過結果觀察,原來主線程的名稱:main;自定義的線程:Thread-0,線程多個時,數字順延。如Thread-1......
進行多線程編程時,不要忘記了Java程序運行是從主線程開始,main方法就是主線程的線程執行內容。
1.6 建立線程方式—實現Runnable接口
建立線程的另外一種方法是聲明實現 Runnable 接口的類。該類而後實現 run 方法。而後建立Runnable的子類對象,傳入到某個線程的構造方法中,開啓線程。
爲什麼要實現Runnable接口,Runable是啥玩意呢?繼續API搜索。
查看Runnable接口說明文檔:Runnable接口用來指定每一個線程要執行的任務。包含了一個 run 的無參數抽象方法,須要由接口實現類重寫該方法。
接口中的方法
Thread類構造方法
建立線程的步驟。
一、定義類實現Runnable接口。
二、覆蓋接口中的run方法。。
三、建立Thread類的對象
四、將Runnable接口的子類對象做爲參數傳遞給Thread類的構造函數。
五、調用Thread類的start方法開啓線程。
代碼演示:
1 public class Demo02 { 2 public static void main(String[] args) { 3 //建立線程執行目標類對象 4 Runnable runn = new MyRunnable(); 5 //將Runnable接口的子類對象做爲參數傳遞給Thread類的構造函數 6 Thread thread = new Thread(runn); 7 Thread thread2 = new Thread(runn); 8 //開啓線程 9 thread.start(); 10 thread2.start(); 11 for (int i = 0; i < 10; i++) { 12 System.out.println("main線程:正在執行!"+i); 13 } 14 } 15 }
自定義線程執行任務類
1 public class MyRunnable implements Runnable{ 2 3 //定義線程要執行的run方法邏輯 4 @Override 5 public void run() { 6 7 for (int i = 0; i < 10; i++) { 8 System.out.println("個人線程:正在執行!"+i); 9 } 10 } 11 }
1.6.1 實現Runnable的原理
爲何須要定一個類去實現Runnable接口呢?繼承Thread類和實現Runnable接口有啥區別呢?
實現Runnable接口,避免了繼承Thread類的單繼承侷限性。覆蓋Runnable接口中的run方法,將線程任務代碼定義到run方法中。
建立Thread類的對象,只有建立Thread類的對象才能夠建立線程。線程任務已被封裝到Runnable接口的run方法中,而這個run方法所屬於Runnable接口的子類對象,因此將這個子類對象做爲參數傳遞給Thread的構造函數,這樣,線程對象建立時就能夠明確要運行的線程的任務。
1.6.2 實現Runnable的好處第二種方式實現Runnable接口避免了單繼承的侷限性,因此較爲經常使用。實現Runnable接口的方式,更加的符合面向對象,線程分爲兩部分,一部分線程對象,一部分線程任務。繼承Thread類,線程對象和線程任務耦合在一塊兒。一旦建立Thread類的子類對象,既是線程對象,有又有線程任務。實現runnable接口,將線程任務單獨分離出來封裝成對象,類型就是Runnable接口類型。Runnable接口對線程對象和線程任務進行解耦。