天天看點

建立多線程的三種方式

在java中給我們提供了三種方式來建立多線程。前兩種是我們比較常見的,第三種是jdk1.5之後提供給我們的。接下來我們詳細的看一下這三種建立線程的寫法。

第一種方式是繼承thread類的寫法,代碼如下:

注意這裡我們覆寫的是run方法,而不是start方法,并且也千萬不要覆寫start方法。為什麼不能覆寫start方法呢?我們看一下thread源碼中start方法是怎麼寫的:

注意這個方法是加鎖的方法。在這個方法中最重要的一段代碼是:start0();我們接着再來看一下start0()這個方法:

它是個native方法。就是這個native的start0()方法,它實作了啟動線程,申請棧記憶體、運作run方法、修改線程狀态等職責。線程管理和棧記憶體管理都是由jvm負責的。如果你覆寫了start方法,也就是撤銷了線程管理和棧記憶體管理的能力,這樣還如何啟動一個線程呢?不過thread的這個設計是很精妙的,因為你隻需要關注你的業務邏輯就行了,而對于線程和棧記憶體的管理都有jvm來做就行了。如果在你的開發中不得不要覆寫start方法的話,請千萬要記得調用super.start(),要不然你的線程無法啟動。

第二種建立線程的方式是實作runnable接口。具體代碼請看下面:

這種寫法是實作了runnable接口的一種寫法。它的原理是什麼呢?我們來看一下thread源碼中run的寫法:

在run方法中我們可以看到如果target != null就調用target.run()方法。而這個target是從哪兒來的呢?我們繼續看thread的源碼,發現在init的方法中有這樣一句話:

接下來我們再看init()這個方法是在哪兒被調用的?通過翻讀源碼我們可以發現在thread的每一個構造函數中都會調用init這個方法,并且有這樣一個構造函數:

看到了吧。我們就是在建立thread的時候,通過thread的構造函數傳遞進去的runnable的實作類。而線程啟動的時候,調用run方法,run方法又接着調用runnable實作類的run方法!!!!

在jdk1.5之後又給我們提供了一種新的建立線程的方式:實作callable方法。具體代碼如下:

在上面的代碼中,在建立futuretask對象的時候,我們把callable的一個匿名實作類當做參數傳到了futuretask的構造函數中,而在啟動線程的時候,我們又把建立的futuretask的對象當做參數傳到了thread的構造函數中。在這裡請注意我們此時不是覆寫的run方法,而是一個叫call的方法。大家可能會感到疑惑這個futuretask和callable這兩個到底是個什麼玩意?下面我們一個一個的分析:

通過翻讀futuretask的源碼我們可以看出來實作了runnablefuture接口,而runnablefuture接口又繼承了runnable和future接口。注意:這裡是繼承了兩個接口!你可能會有疑問java中不是沒有多繼承嗎?不錯,java中類是沒有多繼承的,而對于接口是有多繼承的!!!到這裡我們明白一件事,那就是futuretask是runnable接口的一個實作類。到這裡你是不是明白了點什麼呢?

如果你不明白的話,那就多看幾遍第二種建立線程的方式和它的原理吧。接下來我們來看一下futuretask這個類中的run方法是怎麼寫的:

上面這個方法中的代碼我沒有貼全,隻貼出來了主要的部分。在這個方法中我們會發現這樣的兩句話callable<v> c = callable;result = c.call();這兩句話就是關鍵!!!通過翻讀源碼我們就會發現源碼這個callable就是我們剛才傳到futuretask中的callable的實作類啊!

c.call()那不就是調用的callable實作類的call方法嗎?!!!到這裡終于真相大白了!futuretask中其他方法有興趣的同學可以繼續研究一下。

在callable這個接口中隻有一個call方法。

在實際編碼中,我們看到建立線程更多的是使用第二種方式,因為它更符合java中面向接口程式設計的思想。

最後出個題考一下大家,請說出下面代碼的運作結果: