Java並發編程之線程創建和啟動(Thread、Runnable、Callable和Future)
這一系列的文章暫不涉及Java多線程開發中的底層原理以及JMM、JVM部分的解析(將另文總結),主要關註實際編碼中Java並發編程的核心知識點和應知應會部分。
說在前面,Java並發編程的實質,是線程對象調用start方法啟動多線程,而線程對象則必須是Thread類或其子類實現。Runnable和Callable的作用類似於Comparable、Serializable,是用於被並發的類實現的接口,從而使得Thread類可以在初始化時傳入這個被並發的類。此是大前提。本文從多線程實現和啟動出發,對這些類或接口予以說明。
Thread
通過Thread的子類創建多線程的步驟如下:
1. 創建Thread的子類,並重寫run()方法,該方法即為線程執行體。
2. 創建Thread子類的對象,即為線程對象。
3. 調用線程對象的start()方法啟動線程。
1 public class TestThread extends Thread{ 2 3 public TestThread(String name) { 4 setName(name); 5 } 6 @Override 7 public void run() { 8 while(!interrupted()) 9 System.out.println(getName() + "線程執行了");10 } 11 public static void main(String[] args) { 12 13 TestThread t1 = new TestThread("first"); 14 TestThread t2 = new TestThread("second"); 15 //setDaemon()設置線程為守護線程 16 // t1.setDaemon(true); 17 // t2.setDaemon(true); 18 t1.start();19 t2.start(); 20 t1.interrupt(); 21 } 22 }
Runnable
需要並發執行的類,可以通過實現Runnable接口,作為Thread的Target來創建線程對象。
1 public class TestRunnable implements Runnable{ 2 3 @Override 4 public void run() { 5 while(true) { 6 System.out.println("thread running..."); 7 try { 8 Thread.sleep(1000); 9 } catch (InterruptedException e) { 10 e.printStackTrace(); 11 } 12 } 13 } 14 15 public static void main(String[] args) { 16 //傳入TestRunnable對象作為Target, 開啟線程 17 Thread t = new Thread(new TestRunnable()); 18 t.start(); 19 //采用匿名內部類的方式創建和啟動線程 20 new Thread() { 21 @Override 22 public void run() { 23 System.out.println("Thread的匿名內部類"); 24 } 25 }.start(); 26 //父類采用匿名實現Runnable接口, 並由子類繼承 27 new Thread(new Runnable() { 28 29 @Override 30 public void run() { 31 System.out.println("父類的線程"); 32 } 33 }) { 34 @Override 35 public void run() { 36 System.out.println("子類的線程"); 37 } 38 }.start(); 39 } 40 }
Callable和Future
Java5開始提供了Callable接口,用於現有多線程開發的強力補充。Callable接口提供一個call()方法來構造線程執行體。
1. call()方法可以有返回值
2. call()方法可以聲明拋出異常
因此Callable接口沒有繼承Runnable接口,不能直接作為Thread類的Target來構造線程對象,所以Java5提供了Future接口來代表call方法的返回值。
Future提供了FutureTask實現類,該實現類實現了Future接口和Runnable接口,像橋梁一樣把線程執行體和線程對象連接了起來。
Future接口提供了若幹公共方法來操作Callable任務:
boolean cancel(boolean mayInterruptIfRunning): 試圖取消Future裏關聯的Callable任務
V get():返回Callable任務裏call方法的返回值。調用該方法會導致阻塞,必須等子線程完成後才得到返回值
V get(long timeout, TimeUnit unit):最多阻塞timeout和unit指定的時間,超時將拋出TimeoutException異常
boolean isCancelled():Callable任務正常完成前被取消,則返回true
boolean isDone():Callable任務已完成,則返回true
創建並啟動有返回值的線程步驟如下:
1. 創建Callable接口的實現類,並實現call方法作為線程執行體,再創建類的實例。Java8中可通過Lambda表達式進行。
2. 使用FutureTask類來包裝Callable實現類的對象
3. 使用FutureTask作為Thread對象的target
4. 使用FutureTask對象的get方法獲取子線程執行後的返回值
1 public class TestCallable implements Callable<Integer>{ 2 //實現Callable並重寫call方法作為線程執行體, 並設置返回值1 3 @Override 4 public Integer call() throws Exception { 5 System.out.println("Thread is running..."); 6 Thread.sleep(3000); 7 return 1; 8 } 9 10 public static void main(String[] args) throws InterruptedException, ExecutionException { 11 //創建Callable實現類的對象 12 TestCallable tc = new TestCallable(); 13 //創建FutureTask類的對象 14 FutureTask<Integer> task = new FutureTask<>(tc); 15 //把FutureTask實現類對象作為target,通過Thread類對象啟動線程 16 new Thread(task).start(); 17 System.out.println("do something else..."); 18 //通過get方法獲取返回值 19 Integer integer = task.get(); 20 System.out.println("The thread running result is :" + integer); 21 } 22 }
總結一下,雖然繼承Thread類的開發方式相對簡單,但因為Java單繼承的限制,一般建議通過實現Runnable或Callable接口來創建並啟動多線程。
Java並發編程之線程創建和啟動(Thread、Runnable、Callable和Future)