多线程创建
多線程創建
一、獲得多線程的方法有幾種?
共有四種。傳統的是繼承thread類和實現runnable接口,java5以后又有實現callable接口和java的線程池獲得。
二、繼承Thread類
1、步驟
1. 自定義一個類繼承Thread類。2. 重寫Thread類的run方法 , 把自定義線程的任務代碼寫在run方法中3. 創建Thread的子類對象,并且調用start方法開啟線程。疑問: 重寫run方法的目的是什么?
每個線程都有自己的任務代碼,jvm創建的主線程的任務代碼就是main方法中的所有代碼, 自定義線程的任務代碼就寫在run方法中,自定義線程負責了run方法中代碼。
2、注意:
一個線程一旦開啟,那么線程就會執行run方法中的代碼,run方法千萬不能直接調用,直接調用run方法就相當調用了一個普通的方法而已并沒有開啟新的線程。
原因:
查看start方法源碼,
這是一種模板設計模式,類似netty中的繼承一些入棧handler,重寫讀和寫方法一樣。
public void run() {if (target != null) {target.run(); }}注意:這里的target是Thread維護的一個Runnable接口的引用,Runnable target.
如果從構造函數中傳來了Runnable接口的實現類,那么target會引用到這個實現類,在run方法中調用了實現類的run。
如果沒有傳入runnable,那么就要重寫這個run方法,由JVM調用。
三、實現runnable接口
注意:runnable不是線程,線程只有Thread。runnable只是一個接口 ,可以把線程的任務單獨提出來。runnable將可執行的邏輯單元和線程控制分離開來,這也是面向對象思想的體現。(將Runnable實現類對象定義為final,即多個線程操控的是同一個Runnable實現類對象,同一份業務單元,當然,這里也會存在線程安全問題,后續再講)
1、步驟
四、實現Callable接口,本質還是runnable
1、步驟
1.自定義一個類實現callable接口2.實現call方法3.創建futuretask對象,把自定義類當做構造參數傳進去4.創建一個線程 new thread,把futuretask當做構造參數傳進去。5.調用futuretask的run方法。 public void run() {…..try {Callable<V> c = callable;if (c != null && state == NEW) {V result;boolean ran;try {result = c.call();ran = true;} catch (Throwable ex) {result = null;ran = false;setException(ex);}if (ran)執行完畢后,把返回值set到result中。set(result);}} finally {…..}} class MyThread implements Runnable{@Overridepublic void run() {} }class MyThread2 implements Callable<Integer>{@Overridepublic Integer call() throws Exception {System.out.println("******come in call method()");return 1080;} }/*** 多線程中,第3種獲得多線程的方式*/ public class CallableDemo7 {public static void main(String[] args) throws ExecutionException, InterruptedException {FutureTask<Integer> futureTask = new FutureTask(new MyThread2());new Thread(futureTask,"A").start();//get方法放到最后,否則會阻塞主線程的執行,影響效率Integer result = futureTask.get();//獲得call()方法的返回值System.out.println(result);} }Thread的構建只有利用Runnable接口,因此需要Runnable接口和Callable接口的“中間人”,即FutureTask類,實現了Runnable的子接口,同時可以接受Callable接口實現類的形參。因此,即可使用Callable接口生成線程。本質上還是利用了Java的多態性質。
運行成功后如何獲得返回值?
ft.get();FutureTask實現了callable和ruannable接口,其run方法本質還是runnable的run方法。thread調用的是futuretask的run方法。只不過futuretask封裝了返回值以及一些超時方法。
五、線程池(略)
六、Runnable與Callable接口的區別
創建新類MyThread實現runnable接口 class MyThread implements Runnable{@Overridepublic void run() {} } 新類MyThread2實現callable接口 class MyThread2 implements Callable<Integer>{@Overridepublic Integer call() throws Exception {return 200;} }面試題:runnable接口與callable接口的區別?
- 落地方法不一樣:一個是run,一個是call
- 是否有返回值:前者run()方法無返回值,后者call()方法有返回值
- 是否拋異常:前者沒有異常,后者需要處理異常
七、new Thread()與Runnable接口建立線程的區別
Thread本來就是實現了Runnable,包含Runnable的功能是很正常的啊!!至于兩者的真正區別最主要的就是一個是繼承,一個是實現;其他還有一些面向對象的思想,Runnable就相當于一個作業,而Thread才是真正的處理線程,我們需要的只是定義這個作業,然后將作業交給線程去處理,這樣就達到了松耦合,也符合面向對象里面組合的使用,另外也節省了函數開銷,繼承Thread的同時,不僅擁有了作業的方法run(),還繼承了其他所有的方法。綜合來看,用Runnable比Thread好的多。
runnable接口的作用本身是一種策略模式的體現。
八、FutureTask
FutureTask的異步調用
FutureTask,用它就干一件事,異步調用。
main方法就像一個冰糖葫蘆,一個個方法由main串起來。但解決不了一個問題:正常調用掛起堵塞問題。
例子:
(1)老師上著課,口渴了,去買水不合適,講課線程繼續,我可以單起個線程找班長幫忙買水,
水買回來了放桌上,我需要的時候再去get。
(2)4個同學,A算1+20,B算21+30,C算31*到40,D算41+50,是不是C的計算量有點大啊,
FutureTask單起個線程給C計算,我先匯總ABD,最后等C計算完了再匯總C,拿到最終結果
(3)高考:會做的先做,不會的放在后面做
FutureTask原理
在主線程中需要執行比較耗時的操作時,但又不想阻塞主線程時,可以把這些作業交給Future對象在后臺完成,當主線程將來需要時,就可以通過Future對象獲得后臺作業的計算結果或者執行狀態。
一般FutureTask多用于耗時的計算,主線程可以在完成自己的任務后,再去獲取結果。因此get方法常放到最后。
僅在計算完成時才能檢索結果;如果計算尚未完成,則阻塞 get 方法。一旦計算完成,就不能再重新開始或取消計算。get方法而獲取結果只有在計算完成時獲取,否則會一直阻塞直到任務轉入完成狀態,然后會返回結果或者拋出異常。
注意:
只計算一次,緩存上次結果
get方法放到最后,否則會阻塞主線程的執行,影響效率
代碼
import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; import java.util.concurrent.TimeUnit;class MyThread implements Runnable{@Overridepublic void run() {} } class MyThread2 implements Callable<Integer>{@Overridepublic Integer call() throws Exception {System.out.println(Thread.currentThread().getName()+"come in callable");return 200;} }public class CallableDemo {public static void main(String[] args) throws Exception {//FutureTask<Integer> futureTask = new FutureTask(new MyThread2());FutureTask<Integer> futureTask = new FutureTask(()->{System.out.println(Thread.currentThread().getName()+" come in callable");TimeUnit.SECONDS.sleep(4);return 1024;});FutureTask<Integer> futureTask2 = new FutureTask(()->{System.out.println(Thread.currentThread().getName()+" come in callable");TimeUnit.SECONDS.sleep(4);return 2048;});new Thread(futureTask,"zhang3").start();new Thread(futureTask2,"li4").start();//System.out.println(futureTask.get());//System.out.println(futureTask2.get());//1、一般放在程序后面,直接獲取結果//2、只會計算結果一次while(!futureTask.isDone()){System.out.println("***wait");}System.out.println(futureTask.get());System.out.println(Thread.currentThread().getName()+" come over");} }/*** * 在主線程中需要執行比較耗時的操作時,但又不想阻塞主線程時,可以把這些作業交給Future對象在后臺完成, 當主線程將來需要時,就可以通過Future對象獲得后臺作業的計算結果或者執行狀態。一般FutureTask多用于耗時的計算,主線程可以在完成自己的任務后,再去獲取結果。僅在計算完成時才能檢索結果;如果計算尚未完成,則阻塞 get 方法。一旦計算完成, 就不能再重新開始或取消計算。get方法而獲取結果只有在計算完成時獲取,否則會一直阻塞直到任務轉入完成狀態, 然后會返回結果或者拋出異常。 只計算一次 get方法放到最后*/總結