java核心(十二):多線程(第一篇)
阿新 • • 發佈:2019-03-18
實用 implement cti size timer類 離開 syn ace final
一、多線程的實現方式
Java多線程實現方式主要有三種:繼承Thread類、實現Runnable接口、實現Callable接口通過FutureTask包裝器來創建Thread線程。
其中前兩種方式線程執行完後都沒有返回值,後一種是帶返回值的。
1、第一種實現方式:繼承Thread類
- 繼承Java.lang.Thread類,重寫run()方法,將多線程代碼塊寫入到run()方法中。
- 啟動線程方式:實例化本類對象,然後調用start()方法。
/** * 繼承Thread類,重寫run()方法 */ class MyThread extends Thread{ @Overridepublic void run() { for (int i = 0; i < 5; i++) { System.out.println(this.getName()+" , i = "+i); //getName(),獲取當前線程的名稱 } } } public class Test { public static void main(String[] args) throws Exception { MyThread myThread = new MyThread(); MyThread myThread1= new MyThread(); myThread.start(); //Thread-0 myThread1.start(); //Thread-1 } }
//程序運行結果: Thread-0 , i = 0 Thread-1 , i = 0 Thread-1 , i = 1 Thread-0 , i = 1 Thread-0 , i = 2 Thread-0 , i = 3 Thread-0 , i = 4 Thread-1 , i = 2 Thread-1 , i = 3 Thread-1 , i = 4
2、第二種實現方式:實現Runnable接口
- 實現Java.lang.Runnable接口,並重寫run()方法,將多線程代碼塊寫入到run()方法中。
- 啟動線程方式:將本類對象作為參數傳入給Thread的構造方法,來實例化Thread對象,然後調用Thread對象的start()方法。
/** * 實現Runnable接口,重寫run()方法 */ class MyThread implements Runnable{ @Override public void run() { for (int i = 0; i < 5; i++) { System.out.println(this+" , i = "+i); } } } public class Test { public static void main(String[] args) throws Exception { Runnable myThread = new MyThread(); Runnable myThread1 = new MyThread(); new Thread(myThread).start(); new Thread(myThread1).start(); } }
//註:Thread類的構造方法,可以接收Runnable實例對象
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
3、第三種實現方式:實現Callable接口
- 實現java.util.concurrent.Callable接口,並重寫call()方法,將多線程代碼塊寫入到call()方法中。
- 啟動線程方式:(1)創建Callable接口實例;(2)然後,創建FutureTask類的實例,並將Callable接口的實例作為參數傳入FutureTask實例中;(3)最後,將FutureTask實例作為參數傳入給Thread的構造方法,來實例化Thread對象,並調用Thread對象的start()方法,啟動多線程。(4)另外,線程結束後,可以通過FutureTask對象的get()方法,獲取方法call()方法的返回值。
- 註:FutureTask<V>是一個包裝器,它通過接受Callable<V>來創建,它同時實現了Future和Runnable接口。
/** * 實現Callable接口,重寫call()方法 */ class MyThread implements Callable { @Override public Object call() throws Exception { for (int i = 0; i < 5; i++) { System.out.println(this+" , i = "+i); } return this + " 線程結束"; } } public class Test { public static void main(String[] args) throws Exception { //創建Callable實例 Callable<String> myThread = new MyThread(); Callable<String> myThread1 = new MyThread(); //由Callable實例,來創建一個FutureTask<String>實例 //註釋:FutureTask<V>是一個包裝器,它通過接受Callable<V>來創建,它同時實現了Future和Runnable接口。 FutureTask<String> myTask = new FutureTask<>(myThread); FutureTask<String> myTask1 = new FutureTask<>(myThread1); //創建一個Thread對象,並調用start()方法來啟動線程 new Thread(myTask).start(); new Thread(myTask1).start(); //如有必要,等待計算完成,然後檢索其結果 System.out.println(myTask.get()); System.out.println(myTask1.get()); } }
//程序運行結果: com.study.grammar.MyThread@56c2b49a , i = 0 com.study.grammar.MyThread@762e2323 , i = 0 com.study.grammar.MyThread@56c2b49a , i = 1 com.study.grammar.MyThread@56c2b49a , i = 2 com.study.grammar.MyThread@762e2323 , i = 1 com.study.grammar.MyThread@56c2b49a , i = 3 com.study.grammar.MyThread@762e2323 , i = 2 com.study.grammar.MyThread@56c2b49a , i = 4 com.study.grammar.MyThread@762e2323 , i = 3 com.study.grammar.MyThread@56c2b49a 線程結束 com.study.grammar.MyThread@762e2323 , i = 4 com.study.grammar.MyThread@762e2323 線程結束
二、Thread類啟動線程的底層實現
Thread類本質上是實現了Runnable接口的一個實例,代表一個線程的實例。啟動線程的唯一方法就是通過Thread類的start()實例方法。start()方法會調用本類的start0()方法,start0()方法是一個native方法,它將啟動一個新線程,並執行run()方法。/** * Thread類的start0()方法 * Java開發裏面有一門技術成為JNI技術(Java Native Interface),這門技術的特點是:使用Java來調用本機操作系統提供的函數。但是這樣以來就存在一個缺點,即:不能離開特定的操作系統。 * 如果要想線程能夠執行,嚴格來講是由操作系統來啟動線程,並分配系統資源的。 * 即:使用Thread類的start0()方法,不僅僅包括啟動線程的執行代碼,而且還包括調用操作系統函數進行資源的分配。 */ private native void start0();
/** * Thread類的start()方法 */ public synchronized void start() { //此異常屬於RuntimeException的子類,屬於選擇性異常。 if (threadStatus != 0) throw new IllegalThreadStateException(); /* Notify the group that this thread is about to be started * so that it can be added to the group‘s list of threads * and the group‘s unstarted count can be decremented. */ group.add(this); boolean started = false; try { start0(); started = true; } finally { try { if (!started) { group.threadStartFailed(this); } } catch (Throwable ignore) { /* do nothing. If start0 threw a Throwable then it will be passed up the call stack */ } } }
三、Callable接口、Runnable接口,兩者的區別
- 實現多線程時,Runnable接口的方法是run();Callable接口的方法是call()。
- 實現Runnable接口,任務沒有返回值;實現Callable接口,任務可以設置返回值。
- Runnable無法拋出經過檢查的異常,而Callable可以拋出經過檢查的異常。
@FunctionalInterface public interface Callable<V> { V call() throws Exception; } @FunctionalInterface public interface Runnable { public abstract void run(); } Callable接口,返回結果並且可能拋出異常的任務。實現者定義了一個不帶任何參數的叫做 call 的方法。 Callable接口類似於 Runnable,兩者都是為那些其實例可能被另一個線程執行的類設計的。但是 Runnable不會返回結果,並且無法拋出經過檢查的異常。 Executors 類包含一些從其他普通形式轉換成 Callable 類的實用方法。
四、使用ExecutorService、Callable、Future實現有返回結果的線程
ExecutorService、Callable、Future三個接口實際上都是屬於Executor框架。返回結果的線程是在JDK1.5中引入的新特征,有了這種特征就不需要再為了得到返回值而大費周折了。而且自己實現了也可能漏洞百出。 執行Callable任務後,可以獲取一個Future的對象,在該對象上調用get就可以獲取到Callable任務返回的Object了。再結合線程池接口ExecutorService就可以實現傳說中有返回結果的多線程了。 註意:get方法是阻塞的,即:線程無返回結果,get方法會一直等待。 下面提供了一個完整的有返回結果的多線程測試例子,在JDK1.5下驗證過沒問題可以直接使用。代碼如下:import java.util.concurrent.*; import java.util.Date; import java.util.List; import java.util.ArrayList; /** * 通過實現Callable接口,實現有返回值的線程 */ class MyCallable implements Callable<Object> { private String taskNum; MyCallable(String taskNum) { this.taskNum = taskNum; } /** * 重寫多線程方法, */ public Object call() throws Exception { System.out.println(">>>" + taskNum + "任務啟動"); Date dateTmp1 = new Date(); Thread.sleep(1000); Date dateTmp2 = new Date(); long time = dateTmp2.getTime() - dateTmp1.getTime(); System.out.println(">>>" + taskNum + "任務終止"); return taskNum + "任務返回運行結果,當前任務時間【" + time + "毫秒】"; } } public class Test { public static void main(String[] args) throws ExecutionException, InterruptedException { System.out.println("----程序開始運行----"); int taskSize = 5; // 創建一個線程池 ExecutorService pool = Executors.newFixedThreadPool(taskSize); // 創建多個有返回值的任務 List<Future> list = new ArrayList<Future>(); for (int i = 0; i < taskSize; i++) { Callable c = new MyCallable(i + " "); // 執行任務並獲取Future對象 Future f = pool.submit(c); //ExecutorService.submit(),提交一個返回值的任務用於執行,返回一個表示任務的未決結果的 Future。 list.add(f); } // 關閉線程池 pool.shutdown(); // 獲取所有並發任務的運行結果 for (Future f : list) { // 從Future對象上獲取任務的返回值,並輸出到控制臺 System.out.println(">>>" + f.get().toString()); } } }
//程序運行結果 ----程序開始運行---- >>>0 任務啟動 >>>1 任務啟動 >>>2 任務啟動 >>>3 任務啟動 >>>4 任務啟動 >>>4 任務終止 >>>1 任務終止 >>>0 任務終止 >>>3 任務終止 >>>0 任務返回運行結果,當前任務時間【1005毫秒】 >>>1 任務返回運行結果,當前任務時間【1005毫秒】 >>>2 任務終止 >>>2 任務返回運行結果,當前任務時間【1005毫秒】 >>>3 任務返回運行結果,當前任務時間【1005毫秒】 >>>4 任務返回運行結果,當前任務時間【1005毫秒】
代碼說明: 上述代碼中Executors類,提供了一系列工廠方法用於創建線程池,返回的線程池都實現了ExecutorService接口。 public static ExecutorService newFixedThreadPool(int nThreads) 創建固定數目線程的線程池。 public static ExecutorService newCachedThreadPool() 創建一個可緩存的線程池,調用execute 將重用以前構造的線程(如果線程可用)。如果現有線程沒有可用的,則創建一個新線程並添加到池中。終止並從緩存中移除那些已有 60 秒鐘未被使用的線程。 public static ExecutorService newSingleThreadExecutor() 創建一個單線程化的Executor。 public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) 創建一個支持定時及周期性的任務執行的線程池,多數情況下可用來替代Timer類。 ExecutoreService提供了submit()方法,傳遞一個Callable,或Runnable,返回Future。如果Executor後臺線程池還沒有完成Callable的計算,這調用返回Future對象的get()方法,會阻塞直到計算完成。
java核心(十二):多線程(第一篇)