于是我打開Oracle的java官方文檔去尋找答案馏颂,官方的答案是兩種。
- 實現(xiàn)Runnable接口
public class ImplRunnableStyle implements Runnable {
@Override
public void run() {
System.out.println("使用Runnable方式實現(xiàn)多線程");
}
public static void main(String[] args) {
new Thread(new ImplRunnableStyle()).start();
}
}
- 繼承Thread類
public class ExtendsThreadStyle extends Thread {
@Override
public void run() {
System.out.println("使用繼承Thread方式實現(xiàn)多線程");
}
public static void main(String[] args) {
new ExtendsThreadStyle().start();
}
}
在真正的編程環(huán)境中棋傍,推薦使用的方式還是實現(xiàn)Runnable接口的方式救拉,原因這里有3點:
- 從架構(gòu)角度來看,使用Runnable的方式可以將具體的任務(wù)(run方法)和創(chuàng)建瘫拣、運行線程的機(jī)制(Thread類)解耦亿絮,代碼結(jié)構(gòu)更加優(yōu)雅;
- 如果使用Thread繼承的方式,那么每次想創(chuàng)建一個新的任務(wù)都必須創(chuàng)建一個新的獨立線程派昧,這樣就會造成額外的資源損耗黔姜,如果使用Runnable和線程池就可以避免這樣無謂的損耗;
- 由于Java語言不支持多繼承蒂萎,如果使用繼承Thread的方式秆吵,就無法再繼承其他的類,限制了可擴(kuò)展性五慈。
聊到這里我們是不是可以結(jié)束這個問題了帮毁?NO,NO豺撑,用電視劇里面比較拉仇恨的話講,“就這黔牵?”
我們再來看一道程序執(zhí)行邏輯的思考題:
public class BothImplRunnableAndExtendsThread {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("執(zhí)行實現(xiàn)Runnable接口方式的代碼塊");
}
}) {
@Override
public void run() {
System.out.println("執(zhí)行繼承Thread方式的代碼塊");
}
}.start();
}
}
假如我們這兩種方式一起使用聪轿,會執(zhí)行哪個代碼塊?
咋一看這個題是不是感覺有點懵逼猾浦,正常開發(fā)的時候誰會這么寫代碼陆错。。金赦。但是我想說音瓷,如果你能不運行程序就知道這道題的答案,并且可以給出原因夹抗,那說明你對上面講的兩種實現(xiàn)線程的方式才有了真正的理解绳慎。
我先給出程序的執(zhí)行結(jié)果:控制臺會輸出“執(zhí)行繼承Thread方式的代碼塊”,原因呢漠烧?我們?nèi)タ匆幌耇hread類run方法的源碼就了解了:
/**
* If this thread was constructed using a separate
* <code>Runnable</code> run object, then that
* <code>Runnable</code> object's <code>run</code> method is called;
* otherwise, this method does nothing and returns.
* <p>
* Subclasses of <code>Thread</code> should override this method.
*
* @see #start()
* @see #stop()
* @see #Thread(ThreadGroup, Runnable, String)
*/
@Override
public void run() {
if (target != null) {
target.run();
}
}
代碼很簡單杏愤,如果target!=null就執(zhí)行target.run()方法。那target又是什么已脓?看了Thread類的源碼應(yīng)該很明白了吧珊楼,target就是我們傳過去的實現(xiàn)Runnable接口的對象,那剛才那道思考題的答案為什么是“執(zhí)行繼承Thread方式的代碼塊”度液,小伙伴們也應(yīng)該很清楚了厕宗。因為當(dāng)使用繼承Thread方式的時候,我們重寫了run方法堕担,那么Thread類中run方法的代碼邏輯肯定就執(zhí)行不了了已慢。所以只會執(zhí)行我們重寫的run方法代碼中的邏輯。
下面我們再來聊一下網(wǎng)上某些博客中的“觀點”:
- “線程池創(chuàng)建線程也算是一種新建線程的方法”
public class ThreadPool {
public static void main(String[] args) {
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
cachedThreadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println("線程池的方式創(chuàng)建線程");
}
});
}
}
上面是通過線程池創(chuàng)建線程的簡單代碼霹购,國際慣例我們直接看線程池的源碼是怎樣創(chuàng)建線程的:網(wǎng)上還有很多其他的說法例如:“通過Callable和FutureTask創(chuàng)建線程”、“定時器創(chuàng)建線程”等等其實都是一種包裝察蹲,用趙本山的話講“你以為穿上馬甲我就不認(rèn)識你了请垛?”
最后我們總結(jié)下:
我們通過新建Thread類的方式創(chuàng)建一個獨立線程,但是類里面的run方法有兩種方式來實現(xiàn):第一種是重寫Thread類的run方法洽议;第二種是實現(xiàn)Runnable接口的run方法宗收,然后再把該Runnable接口的實例傳給Thread類。除此以外亚兄,所謂的線程池混稽、定時器等工具類也可以創(chuàng)建線程,但是它們的本質(zhì)都逃不過Java官方文檔提到的兩種方式审胚。