實現線程的正確方法:2種
- 實現Runnable接口創建線程
/**
* 實現Runnable接口創建線程
*/
public class MyThread implements Runnable {
@Override
public void run() {
System.out.println("實現Runnable接口創建線程");
}
public static void main(String[] args) {
new Thread(new MyThread()).start();
}
}
- 繼承Thread類創建線程
/**
* 繼承Thread類創建線程
*/
public class MyThread extends Thread {
@Override
public void run() {
System.out.println("繼承Thread類創建線程");
}
public static void main(String[] args) {
new Thread(new MyThread()).start();
}
}
實現Runnable接口創建線程的方法更好
- 解耦:具體執行的任務,也就是run方法應該與線程的創建、運行、銷燬是不相關的;
- 資源節約:如果用繼承Thread這種方法,每次新建個任務只能新建個線程,線程的創建、運行和銷燬的消耗是比較大的;如果使用實現Runnable接口的方法,後續我們可以使用線程池等工具,不用再去新建線程,帶來的損耗會大大降低;
- 擴展性:Java是不支持多繼承的,如果採用繼承Thread類的方法,那麼子類就無法再去繼承其他類,大大的限制了子類的擴展性
兩種方法的本質對比
讓我們看看Thread類裏面的run()是怎麼寫的:
@Override
public void run() {
if (target != null) {
target.run();
}
}
- 代碼非常的簡單,意思是有Runnable類型的target對象,那麼就執行target對象裏面的run();
- 使用實現Runnable接口方法時,因爲傳入了Runnable對象,所以新建線程對象裏面執行了target.run();
- 使用繼承Thread類方法時,因爲子類重寫了Thread類的run(),所以最終執行子類的run();
同時使用兩種方法會怎麼樣?
public class MyThread {
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();
}
}
繼承Thread類創建線程
上述代碼雖然傳入了Runnable對象,但是子類重寫了Thread類的run(),導致該方法不再調用target.run(),所以同時使用兩種方法,只有繼承Thread類方法才生效!
最精準的描述
- 通常可以分爲兩類:實現Runnable接口、繼承Thread類重寫run()
- 準確的講,創建線程只有一種方式:創建Thread對象,而在Thread類裏面,實現run()有兩種不同的情況:
- 實現Runnable接口,並把實例對象傳給Thread類去執行run();
- 繼承Thread類,直接重寫了Thread類的run();
典型的錯誤觀點
- “線程池創建線程”
public class MyThread {
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 100; i++) {
executorService.submit(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
}
});
}
}
}
pool-1-thread-14
pool-1-thread-9
pool-1-thread-8
pool-1-thread-13
pool-1-thread-15
pool-1-thread-16
......
結果表明,線程池確實創建了新的線程,但是這只是表現,其內部是通過實現ThreadFactory接口來創建線程的,如下所示:
public interface ThreadFactory {
/**
* Constructs a new {@code Thread}. Implementations may also initialize
* priority, name, daemon status, {@code ThreadGroup}, etc.
*
* @param r a runnable to be executed by new thread instance
* @return constructed thread, or {@code null} if the request to
* create a thread is rejected
*/
Thread newThread(Runnable r);
}
- “通過Callable和FutureTask創建線程,也算一種新的創建線程的方式”
public class CallableAndFutureTask {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask futureTask = new FutureTask(new Callable() {
@Override
public Object call() throws Exception {
return Thread.currentThread().getName();
}
});
new Thread(futureTask).start();
System.out.println(futureTask.get());
}
}
Thread-0
實現Callable接口並創建FutureTask對象的方法確實也可以創建線程,但是和線程池一樣,這只是對創建Thread對象的一種包裝,如下圖所示:
public class FutureTask<V> implements RunnableFuture<V>
public interface RunnableFuture<V> extends Runnable, Future<V> {
/**
* Sets this Future to the result of its computation
* unless it has been cancelled.
*/
void run();
}
FutureTask實現了RunnableFuture接口,而RunnableFuture繼承與Runnable接口,根據這種傳遞關係,相當於FutureTask實現了Runnable接口,在實現的run()方法中調用了Callable接口的call方法來獲取返回值,並把返回值set到result中,最後通過get()獲取到返回值,如下圖所示:
public void run() {
.......
Callable<V> c = callable;
result = c.call();
ran = true;
if (ran) set(result);
}
......
}
以上兩種說法,只是對於實現Runnable接口和繼承Thread類方法的封裝,本質上不算新的方法!