

于是我打開Oracle的java官方文檔去尋找答案,官方的答案是兩種。
- 實(shí)現(xiàn)Runnable接口
public class ImplRunnableStyle implements Runnable {
@Override
public void run() {
System.out.println("使用Runnable方式實(shí)現(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方式實(shí)現(xiàn)多線程");
}
public static void main(String[] args) {
new ExtendsThreadStyle().start();
}
}
在真正的編程環(huán)境中,推薦使用的方式還是實(shí)現(xiàn)Runnable接口的方式,原因這里有3點(diǎn):
- 從架構(gòu)角度來看,使用Runnable的方式可以將具體的任務(wù)(run方法)和創(chuàng)建、運(yùn)行線程的機(jī)制(Thread類)解耦,代碼結(jié)構(gòu)更加優(yōu)雅;
- 如果使用Thread繼承的方式,那么每次想創(chuàng)建一個(gè)新的任務(wù)都必須創(chuàng)建一個(gè)新的獨(dú)立線程,這樣就會造成額外的資源損耗,如果使用Runnable和線程池就可以避免這樣無謂的損耗;
- 由于Java語言不支持多繼承,如果使用繼承Thread的方式,就無法再繼承其他的類,限制了可擴(kuò)展性。
聊到這里我們是不是可以結(jié)束這個(gè)問題了?NO,NO,用電視劇里面比較拉仇恨的話講,“就這?”
我們再來看一道程序執(zhí)行邏輯的思考題:
public class BothImplRunnableAndExtendsThread {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("執(zhí)行實(shí)現(xiàn)Runnable接口方式的代碼塊");
}
}) {
@Override
public void run() {
System.out.println("執(zhí)行繼承Thread方式的代碼塊");
}
}.start();
}
}
假如我們這兩種方式一起使用,會執(zhí)行哪個(gè)代碼塊?
咋一看這個(gè)題是不是感覺有點(diǎn)懵逼,正常開發(fā)的時(shí)候誰會這么寫代碼。。。但是我想說,如果你能不運(yùn)行程序就知道這道題的答案,并且可以給出原因,那說明你對上面講的兩種實(shí)現(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就是我們傳過去的實(shí)現(xiàn)Runnable接口的對象,那剛才那道思考題的答案為什么是“執(zhí)行繼承Thread方式的代碼塊”,小伙伴們也應(yīng)該很清楚了。因?yàn)楫?dāng)使用繼承Thread方式的時(shí)候,我們重寫了run方法,那么Thread類中run方法的代碼邏輯肯定就執(zhí)行不了了。所以只會執(zhí)行我們重寫的run方法代碼中的邏輯。
下面我們再來聊一下網(wǎng)上某些博客中的“觀點(diǎn)”:
- “線程池創(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)建線程”、“定時(shí)器創(chuàng)建線程”等等其實(shí)都是一種包裝,用趙本山的話講“你以為穿上馬甲我就不認(rèn)識你了?”
最后我們總結(jié)下:
我們通過新建Thread類的方式創(chuàng)建一個(gè)獨(dú)立線程,但是類里面的run方法有兩種方式來實(shí)現(xiàn):第一種是重寫Thread類的run方法;第二種是實(shí)現(xiàn)Runnable接口的run方法,然后再把該Runnable接口的實(shí)例傳給Thread類。除此以外,所謂的線程池、定時(shí)器等工具類也可以創(chuàng)建線程,但是它們的本質(zhì)都逃不過Java官方文檔提到的兩種方式。