創(chuàng)建線程的三種基本方法

這篇文章主要講解了“創(chuàng)建線程的三種基本方法”,文中的講解內(nèi)容簡(jiǎn)單清晰,易于學(xué)習(xí)與理解,下面請(qǐng)大家跟著小編的思路慢慢深入,一起來(lái)研究和學(xué)習(xí)“創(chuàng)建線程的三種基本方法”吧!

創(chuàng)新互聯(lián)是一家專注于成都網(wǎng)站制作、做網(wǎng)站、外貿(mào)營(yíng)銷網(wǎng)站建設(shè)與策劃設(shè)計(jì),通城網(wǎng)站建設(shè)哪家好?創(chuàng)新互聯(lián)做網(wǎng)站,專注于網(wǎng)站建設(shè)十載,網(wǎng)設(shè)計(jì)領(lǐng)域的專業(yè)建站公司;建站業(yè)務(wù)涵蓋:通城等地區(qū)。通城做網(wǎng)站價(jià)格咨詢:13518219792

挺基礎(chǔ)的知識(shí),一開(kāi)始不是很愿意寫,畢竟這種簡(jiǎn)單的知識(shí)大家不一定愿意看,而且容易寫的大眾化,不過(guò)還好梳理一遍下來(lái)還算是有點(diǎn)收獲,比如我看了 Thread  類重寫的 run 方法,才明白為什么可以把任務(wù)(Runnable)和線程本身(Thread)分開(kāi)來(lái)。

創(chuàng)建線程的三種方法

線程英譯是 Thread,這也是 Java 中線程對(duì)應(yīng)的類名,在 java.lang 包下。

注意下它實(shí)現(xiàn)了 Runnable 接口,下文會(huì)詳細(xì)解釋。

創(chuàng)建線程的三種基本方法

線程與任務(wù)合并 — 直接繼承 Thread 類

線程創(chuàng)建出來(lái)自然是需要執(zhí)行一些特定的任務(wù)的,一個(gè)線程需要執(zhí)行的任務(wù)、或者說(shuō)需要做的事情就在 Thread 類的 run 方法里面定義。

這個(gè) run 方法是哪里來(lái)的呢?

事實(shí)上,它并不是 Thread 類自己的。Thread 實(shí)現(xiàn)了 Runnable 接口,run 方法正是在這個(gè)接口中被定義為了抽象方法,而 Thread  實(shí)現(xiàn)了這個(gè)方法。

所以,我們把這個(gè) Runnable 接口稱為任務(wù)類可能更好理解。

創(chuàng)建線程的三種基本方法

如下,就是通過(guò)集成 Thread 類創(chuàng)建一個(gè)自定義線程 Thread1 的示例:

// 自定義線程對(duì)象 class Thread1 extends Thread {     @Override  public void run() {   // 線程需要執(zhí)行的任務(wù)   ......    } }  // 創(chuàng)建線程對(duì)象 Thread1 t1 = new Thread1();

看這里,Thread 類提供了一個(gè)構(gòu)造函數(shù),可以為某個(gè)線程指定名字:

創(chuàng)建線程的三種基本方法

所以,我們可以這樣:

// 創(chuàng)建線程對(duì)象 Thread1 t1 = new Thread1("t1");

這樣,控制臺(tái)打印的時(shí)候就比較明了,一眼就能知道是哪個(gè)線程輸出的。

當(dāng)然了,一般來(lái)說(shuō),我們寫的代碼都是下面這種匿名內(nèi)部類簡(jiǎn)化版本的:

// 創(chuàng)建線程對(duì)象 Thread t1 = new Thread("t1") {  @Override  // run 方法內(nèi)實(shí)現(xiàn)了要執(zhí)行的任務(wù)  public void run() {   // 線程需要執(zhí)行的任務(wù)      ......   } };

線程與任務(wù)分離 — Thread + 實(shí)現(xiàn) Runnable 接口

假如有多個(gè)線程,這些線程執(zhí)行的任務(wù)都是一樣的,那按照上述方法一的話我們豈不是就得寫很多重復(fù)代碼?

所以,我們考慮把線程執(zhí)行的任務(wù)與線程本身分離開(kāi)來(lái)。

class MyRunnable implements Runnable {     @Override     public void run() {         // 線程需要執(zhí)行的任務(wù)      ......     } }  // 創(chuàng)建任務(wù)類對(duì)象 MyRunnable runnable = new MyRunnable(); // 創(chuàng)建線程對(duì)象 Thread t2 = new Thread(runnable);

除了避免了重復(fù)代碼,使用實(shí)現(xiàn) Runnable 接口的方式也比方法一的單繼承 Thread  類更具靈活性,畢竟一個(gè)類只能繼承一個(gè)父類,如果這個(gè)類本身已經(jīng)繼承了其它類,就不能使用第一種方法了。另外,用這種方式,也更容易與線程池等高級(jí) API  相結(jié)合。

因此,一般來(lái)說(shuō),更推薦使用這種方式去創(chuàng)建線程。也就是說(shuō),不推薦直接操作線程對(duì)象,推薦操作任務(wù)對(duì)象。

上述代碼使用匿名內(nèi)部類的簡(jiǎn)化版本如下:

// 創(chuàng)建任務(wù)類對(duì)象 Runnable runnable = new Runnable() {     public void run(){         // 要執(zhí)行的任務(wù)         ......     } };  // 創(chuàng)建線程對(duì)象 Thread t2 = new Thread(runnable);

同樣的,我們也可以為其指定線程名字:

Thread t2 = new Thread(runnable, "t2");

以上兩個(gè) Thread 的構(gòu)造函數(shù)如圖所示:

創(chuàng)建線程的三種基本方法

可以發(fā)現(xiàn),Thread 類的構(gòu)造函數(shù)無(wú)一例外全部調(diào)用了 init 方法,這個(gè)方法到底做了啥?我們點(diǎn)進(jìn)去看看:

創(chuàng)建線程的三種基本方法

它將構(gòu)造函數(shù)傳進(jìn)來(lái)的 Runnable 對(duì)象傳給了一個(gè)成員變量 target。

創(chuàng)建線程的三種基本方法

target 就是 Thread 類中定義的 Runnable 對(duì)象,代表著需要執(zhí)行的任務(wù)(What will be run)。

這個(gè)變量的存在,就是我們能夠把任務(wù)(Runnable)和線程本身(Thread)分開(kāi)的原因所在。看下面這段代碼:

創(chuàng)建線程的三種基本方法

沒(méi)錯(cuò),這就是 Thread 類默認(rèn)實(shí)現(xiàn)的 run 方法。

在使用第一種方法創(chuàng)建線程的時(shí)候,我們定義了一個(gè) Thread 子類并重寫了其父類的 run 方法,所以這個(gè)父類實(shí)現(xiàn)的 run  方法不會(huì)被執(zhí)行,執(zhí)行的是我們自定義的子類中的 run 方法。

而在使用第二種方法創(chuàng)建線程的時(shí)候,我們并沒(méi)有在 Thread 子類中重寫 run 方法,所以父類默認(rèn)實(shí)現(xiàn)的 run 方法就會(huì)被執(zhí)行。

而這段 run 方法代碼的意思就是說(shuō),如果 taget != null,也就是說(shuō)如果 Thread 構(gòu)造函數(shù)中傳入了 Runnable 對(duì)象,那就執(zhí)行這個(gè)  Runnable 對(duì)象的 run 方法。

線程與任務(wù)分離 — Thread + 實(shí)現(xiàn) Callable 接口

雖然 Runnable 挺不錯(cuò)的,但是仍然有個(gè)缺點(diǎn),那就是沒(méi)辦法獲取任務(wù)的執(zhí)行結(jié)果,因?yàn)樗?run 方法返回值是 void。

這樣,對(duì)于需要獲取任務(wù)執(zhí)行結(jié)果的線程來(lái)說(shuō),Callable 就成為了一個(gè)完美的選擇。

Callable 和 Runnable 基本差不多:

創(chuàng)建線程的三種基本方法

和 Runnbale 比起來(lái),Callable 不過(guò)就是把 run 改成了 call。當(dāng)然,最重要的是!和 void run 不同,這個(gè) call  方法是擁有返回值的,而且能夠拋出異常。

這樣,一個(gè)很自然的想法,就是把 Callable 作為任務(wù)對(duì)象傳給 Thread,然后 Thread 重寫 call 方法就完事兒。

But,遺憾的是,Thread 類的構(gòu)造函數(shù)里并不接收 Callable 類型的參數(shù)。

所以,我們需要把 Callable 包裝一下,包裝成 Runnable 類型,這樣就能傳給 Thread 構(gòu)造函數(shù)了。

為此,F(xiàn)utureTask 成為了最好的選擇。

創(chuàng)建線程的三種基本方法

可以看到 FutureTask 間接繼承了 Runnable 接口,因此它也可以看作是一個(gè) Runnable 對(duì)象,可以作為參數(shù)傳入 Thread  類的構(gòu)造函數(shù)。

另外,F(xiàn)utureTask 還間接繼承了 Future 接口,并且,這個(gè) Future 接口定義了可以獲取 call() 返回值的方法 get:

創(chuàng)建線程的三種基本方法

看下面這段代碼,使用 Callable 定義一個(gè)任務(wù)對(duì)象,然后把 Callable 包裝成 FutureTask,然后把 FutureTask 傳給  Thread 構(gòu)造函數(shù),從而創(chuàng)建出一個(gè)線程對(duì)象。

另外,Callable 和 FutureTask 的泛型填的就是 Callable 任務(wù)返回的結(jié)果類型(就是 call 方法的返回類型)。

class MyCallable implements Callable<Integer> {     @Override     public Integer call() throws Exception {         // 要執(zhí)行的任務(wù)         ......         return 100;     } } // 將 Callable 包裝成 FutureTask,F(xiàn)utureTask也是一種Runnable MyCallable callable = new MyCallable(); FutureTask<Integer> task = new FutureTask<>(callable); // 創(chuàng)建線程對(duì)象 Thread t3 = new Thread(task);

當(dāng)線程運(yùn)行起來(lái)后,可以通過(guò) FutureTask 的 get 方法獲取任務(wù)運(yùn)行結(jié)果:

Integer result = task.get();

不過(guò),需要注意的是,get 方法會(huì)阻塞住當(dāng)前調(diào)用這個(gè)方法的線程。比如說(shuō)我們?cè)谥骶€程中調(diào)用了 get 方法去獲取 t3 線程的任務(wù)運(yùn)行結(jié)果,那么只有這個(gè)  call 方法成功返回了,主線程才能夠繼續(xù)往下執(zhí)行。

換句話說(shuō),如果 call 方法一直得不到結(jié)果,那么主線程也就一直無(wú)法向下運(yùn)行。

啟動(dòng)線程

OK,綜上,我們已經(jīng)把線程成功創(chuàng)建出來(lái)了,那么怎么把它啟動(dòng)起來(lái)呢?

以第一種創(chuàng)建線程的方法為例:

// 創(chuàng)建線程 Thread t1 = new Thread("t1") {  @Override  // run 方法內(nèi)實(shí)現(xiàn)了要執(zhí)行的任務(wù)  public void run() {   // 線程需要執(zhí)行的任務(wù)      ......   } };  // 啟動(dòng)線程 t1.start();

這里涉及一道經(jīng)典的面試題,即為什么使用 start 啟動(dòng)線程,而不使用 run 方法啟動(dòng)線程?

使用 run 方法啟動(dòng)線程看起來(lái)好像并沒(méi)啥問(wèn)題,對(duì)吧,run 方法內(nèi)定義了要執(zhí)行的任務(wù),調(diào)用 run 方法不就執(zhí)行了這個(gè)任務(wù)了?

這確實(shí)沒(méi)錯(cuò),任務(wù)確實(shí)能夠被正確執(zhí)行,但是并不是以多線程的方式,當(dāng)我們使用 t1.run() 的時(shí)候,程序仍然是在創(chuàng)建 t1 線程的 main  線程下運(yùn)行的,并沒(méi)有創(chuàng)建出一個(gè)新的 t1 線程。

舉個(gè)例子:

// 創(chuàng)建線程 Thread t1 = new Thread("t1") {     @Override     public void run() {       // 線程需要執(zhí)行的任務(wù)       System.out.println("開(kāi)始執(zhí)行");       FileReader.read(文件地址); // 讀文件     } };  t1.run(); System.out.println("執(zhí)行完畢");

如果使用 run 方法啟動(dòng)線程,"執(zhí)行完畢" 這句話需要在文件讀取完畢后才能夠輸出,也就是說(shuō)讀文件這個(gè)操作仍然是同步的。假設(shè)讀取操作花費(fèi)了 5  秒鐘,如果沒(méi)有線程調(diào)度機(jī)制,這 5 秒 CPU 什么都做不了,其它代碼都得暫停。

而如果使用 start 方法啟動(dòng)線程,"執(zhí)行完畢" 這句話在文件讀取完畢之前就會(huì)被很快地輸出,因?yàn)槎嗑€程讓方法執(zhí)行變成了異步的,讀取文件這個(gè)操作是 t1  線程在做,而 main 線程并沒(méi)有被阻塞。

感謝各位的閱讀,以上就是“創(chuàng)建線程的三種基本方法”的內(nèi)容了,經(jīng)過(guò)本文的學(xué)習(xí)后,相信大家對(duì)創(chuàng)建線程的三種基本方法這一問(wèn)題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是創(chuàng)新互聯(lián),小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!

標(biāo)題名稱:創(chuàng)建線程的三種基本方法
文章路徑:http://bm7419.com/article48/psdchp.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站建設(shè)網(wǎng)站導(dǎo)航、網(wǎng)站維護(hù)、靜態(tài)網(wǎng)站、服務(wù)器托管、自適應(yīng)網(wǎng)站

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)

外貿(mào)網(wǎng)站建設(shè)