三種線程創建的方式
本篇內容為線程專題 -- 線程的創建,狀態,工作過程中的線程創建方式的部分內容。
JAVA中有三種線程創建的方式:
(1)實現Runnable接口的run方法。
(2)繼承Thread類并重寫run的方法。
(3)使用FutureTask方式(實現Callable接口的方式)。
說明:在《Java并發編程之美》書中第(3)種創建方式描述為:使用FutureTask方式。
繼承Thread類的方式的實現
1 /** 2 * 創建線程方式--繼承Thread類的方式的實現 3 * @author JustJavaIt 4 */ 5 public class ThreadTest { 6 7 public static void main(String[] args) { 8 //創建線程 9 MyThread thread = new MyThread(); 10 //啟動線程 11 thread.start(); 12 System.out.println("I am main thread"); 13 } 14 15 /** 16 * 繼承Thread類,并重寫run方法 17 */ 18 public static class MyThread extends Thread{ 19 @Override 20 public void run() { 21 System.out.println("I am a child thread"); 22 } 23 } 24 }

運行結果如下:

如上代碼中的MyThread類繼承了Thread類,并重寫了run()方法。在main函數里面創建了一個MyThread的實例,然后調用該實例的start方法啟動了線程。需要注意的是, 當創建完thread對象后該線程并沒有被啟動執行,直到調用了start方法后才真正啟動了線程。
其實調用start方法后線程并沒有馬上執行而是處于就緒狀態,這個就緒狀態是指該線程已經獲取了除CPU資源外的其他資源,等待獲取CPU資源后才會真正處于運行狀態。 一旦run方法執行完畢,該線程就處于終止狀態。
使用繼承方式的好處是,在run()方法內獲取當前線程直接使用this就可以了,無須使用Thread.currentThread()方法;不好的地方是Java不支持多繼承,如果繼承了Thread類, 那么就不能再繼承其他類。另外任務與代碼沒有分離,當多個線程執行一樣的任務時需要 多份任務代碼,而Runable則沒有這個限制。
實現Runnable接口的run方法方式
1 /** 2 * 創建線程方式--實現Runnable接口的run方法。 3 * @author JustJavaIt 4 * @date 2022/2/12 16:21 5 */ 6 public class RunableTest { 7 public static void main(String[] args) { 8 RunableTask task = new RunableTask(); 9 //任務和代碼分離,當多個線程執行一樣任務時不用多份任務代碼。 10 new Thread(task).start(); 11 new Thread(task).start(); 12 } 13 14 public static class RunableTask implements Runnable{ 15 16 @Override 17 public void run() { 18 System.out.println("I am a child thread"); 19 } 20 } 21 }

運行結果如下:

如上代碼所示,兩個線程共用一個task代碼邏輯,如果需要,可以給RunableTask添加參數進行任務區分。另外,RunableTask可以繼承其他類。但是上面介紹的兩種方式都有一個缺點,就是任務沒有返回值。
使用FutureTask方式(實現Callable接口的方式)
通過實現callable接口的方式,可以創建一個線程,需要重寫其中的call方式。啟動線程時,需要創建一個Callable的實例,再用FutureTask實例包裝它,最終,再包裝成Thread實例,調用start()啟動,并且,可以通過FutureTask的get方法來獲取返回值。
FutureTask介紹
在Java并發程序中FutureTask表示一個可以取消的異步運算。它有啟動和取消運算、查詢運算是否完成和取回運算結果等方法。只有當運算完成的時候結果才能取回,如果運算尚未完成get方法將會阻塞。一個FutureTask對象可以對調用了Callable和Runnable的對象進行包裝,由于FutureTask也是調用了Runnable接口所以它可以提交給Executor來執行。
1 /** 2 * 創建線程方式--使用FutureTask方式 Page21 3 * @author JustJavaIt 4 */ 5 public class FutureTasktest { 6 public static void main(String[] args) { 7 CallerTask callerTask = new CallerTask(); 8 //創建異步任務 9 FutureTask<String> futureTask = new FutureTask<>(callerTask); 10 //啟動線程 11 new Thread(futureTask).start(); 12 String result; 13 try { 14 //等待任務執行完畢,并返回結果。 15 result = futureTask.get(); 16 System.out.println("result:"+ result); 17 } catch (InterruptedException e) { 18 e.printStackTrace(); 19 } catch (ExecutionException e) { 20 e.printStackTrace(); 21 } 22 } 23 24 /** 25 * 創建任務類,類似Runable 26 */ 27 public static class CallerTask implements Callable<String>{ 28 29 @Override 30 public String call() throws Exception { 31 return "hello"; 32 } 33 } 34 }

運行結果如下:

如上代碼中的CallerTask類實現了Callable接口的call()方法。在main函數內首先創建了一個FutrueTask對象(構造函數為CallerTask的實例),然后使用創建的FutrueTask對象作為任務創建了一個線程并且啟動它,最后通過futureTask.get()等待任務執行完畢并返回結果。
小結:使用繼承方式的好處是方便傳參,你可以在子類里面添加成員變量,通過set方法設置參數或者通過構造函數進行傳遞,而如果使用Runnable方式,則只能使用主線程里面被聲明為final的變量。不好的地方是Java不支持多繼承,如果繼承了Thread類, 那么子類不能再繼承其他類,而Runable則沒有這個限制。前兩種方式都沒辦法拿到任務的返回結果,但是Futuretask方式可以。
補充
FutureTask和Future示例
FutureTask是Future和Callable的結合體和Callable的結合體
1 /** 2 * FutureTask示例 3 * @author JustJavait 4 * @date 2022/1/12 18:02 5 */ 6 public class CallableFutureTask { 7 public static void main(String[] args) { 8 //第一種方式 線程池 9 ExecutorService executor = Executors.newCachedThreadPool(); 10 Task1 task1 = new Task1(); 11 FutureTask<Integer> futureTask = new FutureTask<>(task1); 12 executor.submit(futureTask); 13 executor.shutdown(); 14 15 //第二種方式,注意這種方式和第一種方式效果是類似的,只不過一個使用的是ExecutorService,一個使用的是Thread 16 /* Task1 task1 = new Task1(); 17 FutureTask<Integer> futureTask = new FutureTask<>(task1); 18 Thread thread = new Thread(futureTask); 19 thread.start();*/ 20 21 try { 22 Thread.sleep(1000); 23 } catch (InterruptedException e1) { 24 e1.printStackTrace(); 25 } 26 27 System.out.println("主線程在執行任務"); 28 29 try { 30 System.out.println("task運行結果" + futureTask.get()); 31 } catch (InterruptedException e) { 32 e.printStackTrace(); 33 } catch (ExecutionException e) { 34 e.printStackTrace(); 35 } 36 37 System.out.println("所有任務執行完畢"); 38 } 39 } 40 41 class Task1 implements Callable<Integer> { 42 @Override 43 public Integer call() throws Exception { 44 System.out.println("子線程在進行計算"); 45 Thread.sleep(3000); 46 int sum = 0; 47 for (int i = 0; i < 100; i++) { 48 sum += i; 49 } 50 return sum; 51 } 52 }
1 /** 2 * Future示例 3 * @author JustJavait 4 * @date 2022/1/12 17:55 5 */ 6 public class CallableFuture { 7 public static void main(String[] args) { 8 ExecutorService executor = Executors.newCachedThreadPool(); 9 Task task = new Task(); 10 Future<Integer> result = executor.submit(task); 11 executor.shutdown(); 12 13 try { 14 Thread.sleep(1000); 15 } catch (InterruptedException e1) { 16 e1.printStackTrace(); 17 } 18 19 System.out.println("主線程在執行任務"); 20 21 try { 22 System.out.println("task運行結果" + result.get()); 23 } catch (InterruptedException e) { 24 e.printStackTrace(); 25 } catch (ExecutionException e) { 26 e.printStackTrace(); 27 } 28 29 System.out.println("所有任務執行完畢"); 30 } 31 } 32 33 class Task implements Callable<Integer> { 34 @Override 35 public Integer call() throws Exception { 36 System.out.println("子線程在進行計算"); 37 Thread.sleep(3000); 38 int sum = 0; 39 for (int i = 0; i < 100; i++) { 40 sum += i; 41 } 42 return sum; 43 } 44 }
運行結果都為:

三種方式的區別?
Java中,類僅支持單繼承,如果一個類繼承了Thread類,就無法再繼承其它類,因此,如果一個類既要繼承其它的類,又必須創建為一個線程,就可以使用實現Runable接口的方式。
使用實現Runable接口的方式創建的線程可以處理同一資源,實現資源的共享。
使用實現Callable接口的方式創建的線程,可以獲取到線程執行的返回值、是否執行完成等信息。
Java中Runnable和Callable有什么不同?
Runnable和Callable都是創建線程的方式。Runnable從JDK1.0開始就有了,Callable是在JDK1.5增加的。
(1)實現Callable接口的任務線程能返回執行結果;而實現Runnable接口的任務線程不能返回結果;
(2)Callable接口的call()方法允許拋出異常;而Runnable接口的run()方法的異常只能在內部消化,不能繼續上拋;
<END>
??希望本文章對您有幫助,您的「 轉發、點贊 」是我創作的無限動力。
掃描下方二維碼關注微信公眾號,您會收到更多優質文章推送。


浙公網安備 33010602011771號