網上和書籍的各種說法:魚龍混雜
正確的說法
方法一:實現Runnable接口
package threadcoreknowledge.createthreads;
/**
* 描述: 用Runnable方式創建線程
*/
public class RunnableStyle implements Runnable{
public static void main(String[] args) {
Thread thread = new Thread(new RunnableStyle());
thread.start();
}
@Override
public void run() {
System.out.println("用Runnable方法實現線程");
}
}
方法二:繼承Thread
package threadcoreknowledge.createthreads;
/**
* 描述: 用Thread方式實現線程
*/
public class ThreadStyle extends Thread{
@Override
public void run() {
System.out.println("用Thread類實現線程");
}
public static void main(String[] args) {
new ThreadStyle().start();
}
}
實現Runnable接口更好一些原因如下:
run方法源碼解析:
/* What will be run. */
private Runnable target;
@Override
public void run() {
if (target != null) {
target.run();
}
}
實現Runnable接口最終會調用 target.run() 但是繼承Thread類的run方法整個都被重寫。
繼承Thread有如下缺點:
1、從代碼的架構考慮的話:run方法裏面的內容(對應我們具體執行的任務),它應該和我們和線程的創建、運行等機制也就是Thread類是解耦的,所以不應該把這兩個事情混爲一談,從解耦的角度是有缺點的。
2、如果說繼承了Thread類,用這種方法的話,每次如果我們需要新建一個任務,只能去新建一個獨立的線程,而新建一個獨立的線程這樣的損耗是比較大的,需要創建、執行、銷燬等,而如果使用Runnable接口的話,後面就會使用線程池等一些工具,就可以大大減小創建線程帶來的損耗,從資源節約的角度上也是優缺點的。
3、Java不支持多繼承,如果繼承了Thread類就不可以再去繼承其他的類,大大限制了可擴展性。
同時使用這兩種方法會怎麼樣:
/**
* 描述: 同時使用Runnable和Thread兩種實現線程的方式
*/
public class BothRunnableThread {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("我來自Runnable");
}
}) {
@Override
public void run() {
System.out.println("我來自Thread");
}
}.start();
}
}
運行之後:
分析:這是因爲run方法被重寫了,這是target.run就不會執行了。
常見錯誤觀點:
1、“線程池創建線程也是一種新建線程的方式”
雖然利用線程成可以創建線程,但是這只是表象,不屬於創建線程的本質。通過深入Executors
的源碼可以看到,在線程池的內部,也是新建了Thread類來執行這些任務的。
2、“通過Callable和FutureTAsk創建線程,也算是一種新建線程的方式。”
3、“無返回值是實現runnable接口,有返回值是實現callable接口,所以callable是新的實現線程的方式”
4、“匿名內部類”
5、“Lambda表達式”
綜上所述,這些錯誤觀點的本質都是離不開Runnable接口和Thread類的。都屬於表明現象。多線程的實現方式,在代碼中寫法千變萬化,但本質不變。