溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

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

發(fā)布時(shí)間:2021-06-25 09:42:11 來源:億速云 閱讀:117 作者:chen 欄目:web開發(fā)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

所以,我們可以這樣:

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

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

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

// 創(chuàng)建線程對象 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ù)與線程本身分離開來。

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

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

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

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

// 創(chuàng)建任務(wù)類對象 Runnable runnable = new Runnable() {     public void run(){         // 要執(zhí)行的任務(wù)         ......     } };  // 創(chuàng)建線程對象 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ù)無一例外全部調(diào)用了 init 方法,這個(gè)方法到底做了啥?我們點(diǎn)進(jìn)去看看:

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

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

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

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

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

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

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

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

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

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

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

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

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

Callable 和 Runnable 基本差不多:

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

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

這樣,一個(gè)很自然的想法,就是把 Callable 作為任務(wù)對象傳給 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 對象,可以作為參數(shù)傳入 Thread  類的構(gòu)造函數(shù)。

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

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

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

另外,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)建線程對象 Thread t3 = new Thread(task);

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

Integer result = task.get();

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

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

啟動(dòng)線程

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

以第一種創(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)線程看起來好像并沒啥問題,對吧,run 方法內(nèi)定義了要執(zhí)行的任務(wù),調(diào)用 run 方法不就執(zhí)行了這個(gè)任務(wù)了?

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

舉個(gè)例子:

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

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

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

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

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI