今天簡單說一下Java三種多線程實現方式和區別,主要有實現Runnable、Callable和繼承Thread三種方式。
實現Runnable的方式
這種方式比較常用,當我們的線程類有繼承其他的類的情況下(Java不支持類多繼承),並且線程任務不需要返回值的情況下可以選用這種方式。
1 public class ThreadRunnableDemo implements Runnable{ 2 3 /** 計數變量 */ 4 private int count = 0; 5 6 public static void main(String[] args) throws InterruptedException { 7 8 ThreadRunnableDemo threadRunnableDemo = new ThreadRunnableDemo(); 9 10 //實例化線程 11 Thread thread = new Thread(threadRunnableDemo, "threadRunnableDemoA"); 12 System.out.println(String.format("線程狀態preStart: %s", thread.getState())); 13 14 //啟動線程 15 thread.start(); 16 System.out.println(String.format("線程狀態afterStart: %s", thread.getState())); 17 18 //主線程休眠1000ms 19 Thread.sleep(1000); 20 System.out.println(String.format("線程狀態after1000ms: %s", thread.getState())); 21 22 } 23 24 @Override 25 public void run() { 26 27 count++; 28 29 System.out.println(String.format("線程名稱:%s, 線程狀態:%s, count:%s", 30 Thread.currentThread().getName(), Thread.currentThread().getState(), count)); 31 32 } 33 }
輸出結果:
1 線程狀態preStart: NEW 2 線程狀態afterStart: RUNNABLE 3 線程名稱:threadRunnableDemoA, 線程狀態:RUNNABLE, count:1 4 線程狀態after1000ms: TERMINATED
實現Callable的方式
當我們執行線程需要返回值的時候那么就必須選用實現Callable類的方式,因為目前只有這種方式能返回值。當然這種方式我們也可以不需要獲取返回值。
這種方式是通過FutureTask的get()方法(下面代碼的第22行)或者get(long timeout, TimeUnit unit)(下面代碼的第28行)方法獲取返回值。當我們看Callable的接口定義的源碼會發現“public interface Callable<V> ” ,我們實現的時候是需要定義返回類型,如下面代碼所示。
除此之外我們還需要注意的是:當我們通過FutureTask的get()方法去獲取線程的返回值的時候是要等到線程call()內容都執行完畢之后才能獲取得到,並且get()方法后面的代碼必須等待,說明這一定是同步的,所以我們可以在真正需要線程返回值的時候才通過get()方法去獲取,以免被阻塞。當我們通過get(long timeout, TimeUnit unit)方式去獲取的時候可以設置超時時間,如果超過所設置的超時時間都沒有獲取到線程返回的值則會拋出 java.util.concurrent.TimeoutException 異常,當然如果在get(long timeout, TimeUnit unit)之前用get()方式獲取了的話就不會拋異常。
實現Callable還有個好處就是可以線程可以拋異常,如果我們需要在線程里拋出異常的話也可以選用這種方式,其他兩種方式只能捕獲異常信息。
1 public class ThreadCallableDemo implements Callable<Integer>{ 2 3 /** 計數變量 */ 4 private int count = 0; 5 6 public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException { 7 8 ThreadCallableDemo threadCallableDemo = new ThreadCallableDemo(); 9 10 //通過FutureTask獲取返回值 11 FutureTask<Integer> taskA = new FutureTask<>(threadCallableDemo); 12 13 //實例化線程 14 Thread thread = new Thread(taskA, "threadCallableDemoA"); 15 System.out.println(String.format("線程狀態preStart: %s", thread.getState())); 16 17 //啟動線程 18 thread.start(); 19 System.out.println(String.format("線程狀態afterStart: %s", thread.getState())); 20 21 //通過FutureTask的get()方法獲取返回值 22 int result = taskA.get(); 23 System.out.println("是否同步測試...."); 24 System.out.println(String.format("result: %s", result)); 25 System.out.println(String.format("線程狀態afterGetResult1: %s", thread.getState())); 26 27 //通過FutureTask的get()方法獲取返回值 設置超時時間 單位為ms 28 int resultWithTime = taskA.get(100, TimeUnit.MILLISECONDS); 29 System.out.println(String.format("resultWithTime: %s", resultWithTime)); 30 System.out.println(String.format("線程狀態afterGetResult2: %s", thread.getState())); 31 32 } 33 34 /** 35 * 實現Callable的call類 36 */ 37 @Override 38 public Integer call() throws Exception { 39 40 //自增 41 count++; 42 43 System.out.println(String.format("線程名稱:%s, 線程狀態:%s, count:%s", 44 Thread.currentThread().getName(), Thread.currentThread().getState(), count)); 45 System.out.println("休眠1000ms...."); 46 Thread.currentThread().sleep(1000); 47 return count; 48 } 49 }
輸出結果:
1 線程狀態preStart: NEW 2 線程狀態afterStart: RUNNABLE 3 線程名稱:threadCallableDemoA, 線程狀態:RUNNABLE, count:1 4 休眠1000ms.... 5 是否同步測試.... 6 result: 1 7 線程狀態afterGetResult1: TERMINATED 8 resultWithTime: 1 9 線程狀態afterGetResult2: TERMINATED
繼承Thread的方式
Thread類實際上也是實現Runnable接口,所以當我們繼承Thread的時候我們即使不實現run()方法也不會報錯,這種方式也經常用。
下面我寫了兩種不同繼承Thread的代碼,大家可以看一下區別,我在網上看到很多人說 繼承Thread實現多線程,線程間不能共享數據,但是我用下面的代碼1方式似乎也可以共享哇,歡迎大家提出質疑。
代碼1:
1 public class ThreadThreadDemo extends Thread{ 2 3 /** 計數變量 */ 4 private int count = 0; 5 6 public static void main(String[] args) throws InterruptedException { 7 8 ThreadThreadDemo threadThreadDemo = new ThreadThreadDemo(); 9 10 //實例化線程 11 Thread thread = new Thread(threadThreadDemo, "threadThreadDemoA"); 12 System.out.println(String.format("線程狀態preStart: %s", thread.getState())); 13 14 //啟動線程 15 thread.start(); 16 System.out.println(String.format("線程狀態afterStart: %s", thread.getState())); 17 18 //主線程休眠1000s 19 Thread.sleep(1000); 20 System.out.println(String.format("線程狀態after1000ms: %s", thread.getState())); 21 } 22 23 @Override 24 public void run() { 25 26 count++; 27 28 System.out.println(String.format("線程名稱:%s, 線程狀態:%s, count:%s", 29 Thread.currentThread().getName(), Thread.currentThread().getState(), count)); 30 } 31 }
輸出結果1:
1 線程狀態preStart: NEW 2 線程狀態afterStart: RUNNABLE 3 線程名稱:threadThreadDemoA, 線程狀態:RUNNABLE, count:1 4 線程狀態after1000ms: TERMINATED
代碼2:
1 public class ThreadThreadDemo extends Thread{ 2 3 /** 計數變量 */ 4 private int count = 0; 5 6 public static void main(String[] args) throws InterruptedException { 7 8 ThreadThreadDemo threadThreadDemo = new ThreadThreadDemo(); 9 10 //實例化線程 11 System.out.println(String.format("線程狀態preStart: %s", threadThreadDemo.getState())); 12 13 //啟動線程 14 threadThreadDemo.start(); 15 System.out.println(String.format("線程狀態afterStart: %s", threadThreadDemo.getState())); 16 17 //主線程休眠1000s 18 Thread.sleep(1000); 19 System.out.println(String.format("線程狀態after1000ms: %s", threadThreadDemo.getState())); 20 } 21 22 @Override 23 public void run() { 24 25 count++; 26 27 System.out.println(String.format("線程名稱:%s, 線程狀態:%s, count:%s", 28 Thread.currentThread().getName(), Thread.currentThread().getState(), count)); 29 } 30 }
輸出結果2:
1 線程狀態preStart: NEW 2 線程狀態afterStart: RUNNABLE 3 線程名稱:Thread-0, 線程狀態:RUNNABLE, count:1 4 線程狀態after1000ms: TERMINATED
最后總結:
- 如果不要求線程返回結果,也不需要拋異常也沒有繼承其他的類,那么三種方式可以任選,看喜好;
- 如果有繼承其他類,那么就只能用實現Runnable和實現Callable的方式;
- 如果需要線程返回結果或者需要線程拋異常那么選擇實現Callable的方式的方式,但是需要注意的是獲取返回結果是同步的方式。
如果有疑問或者有問題歡迎留言討論!
下面是我的個人公眾號,歡迎關注,一起學習,可以方便看我的文章!

