線程的創建方式
線程的創建方式有四種,分別是繼承Thread類、實現Runnable接口、實現callable接口、線程池,在這里我們只探討前面三種方式。
1. 繼承Thread類
首先是使用繼承Thread類創建線程,我們需要繼承Thread類還要重寫run方法,然后在main方法中創建線程並調用start()方法來啟動線程。
public class Demo {
public static class MyThread extends Thread {
@Override
public void run() {//重寫run方法
System.out.println("MyThread");
}
}
public static void main(String[] args) {
Thread myThread = new MyThread();
myThread.start();//調用start方法
}
}
2. 實現callable接口
使用實現Runnable接口來創建線程,首先我們創建一個類來實現Runnable接口的run方法,然后也需要在main方法中調用start方法來啟動線程。
public class Demo {
public static class MyThread implements Runnable {
@Override
public void run() {
System.out.println("MyThread");
}
}
public static void main(String[] args) {
new MyThread().start();
//因為Runnable接口是一個函數式接口,所以我們可以使用這種方式來創建線程。
new Thread(() -> {
System.out.println("線程運行");
}).start();
}
}
3. 實現callable接口
通常我們都是我們使用Runnable和Thread來創建一個新的線程。但是它們有一個弊端,就是run方法是沒有返回值的。
但是有時候我們希望開啟一個線程去執行一個任務,並且這個任務執行完成后有一個返回值。
這時候我們就可以使用實現callable接口的方式來創建線程。
我們可以使用ExecutorService可以使用submit方法來讓一個Callable接口執行。
它會返回一個Future,我們后續的程序可以通過這個Future的get方法得到結果。
class Task implements Callable<Integer>{
@Override
public Integer call() throws Exception {
// 使得線程睡眠一秒
Thread.sleep(1000);
//返回一個2
return 2;
}
public static void main(String args[]){
ExecutorService executor = Executors.newCachedThreadPool();
Task task = new Task();
Future<Integer> result = executor.submit(task);
// 注意調用get方法會阻塞當前線程,直到得到結果。
System.out.println(result.get());
}
}
我們還可以使用一個類FutureTask配合實現callable接口的方式來創建線程。
class Task implements Callable<Integer>{
@Override
public Integer call() throws Exception {
// 使得線程睡眠一秒
Thread.sleep(1000);
return 2;
}
public static void main(String args[]){
ExecutorService executor = Executors.newCachedThreadPool();
FutureTask<Integer> futureTask = new FutureTask<>(new Task());
executor.submit(futureTask);
System.out.println(futureTask.get());
}
}
兩種方式在使用上有一點區別。首先,調用submit方法是沒有返回值的,我們不用接收返回值去調用get方法。
這里實際上是調用的submit(Runnable task)方法,而上面的Demo,調用的是submit(Callable
然后,這里是使用FutureTask直接取get取值,而上面的Demo是通過submit方法返回的Future去取值。
在很多高並發的環境下,有可能Callable和FutureTask會創建多次。FutureTask能夠在高並發環境下確保任務只執行一次。
三種方式之間的比較
- 實現接口方式的好處
- 由於Java“單繼承,多實現”的特性,Runnable接口使用起來比Thread更靈活。
- Runnable接口出現更符合面向對象,將線程單獨進行對象的封裝。
- Runnable接口出現,降低了線程對象和線程任務的耦合性。
- 如果使用線程時不需要使用Thread類的諸多方法,顯然使用Runnable接口更為輕量。
- Runnable和Callable接口的比較
- Callable接口可以獲取線程運行的信息以及中止線程,Runnable只能提供基本的線程運行工作,Callable的功能更豐富一些
- Callable的call()方法允許拋出異常,Runnable的run()方法則不允許
- 當使用FutureTask.get()方法時,主線程會阻塞,因為該方法返回的是該線程的運行結果,只有等到該線程結束才可以返回結果,而該方法寫在主線程中,主線程會因為該方法等待線程結束而阻塞,直到返回出了運行結果,主程序才會繼續運行,所以FutureTask.get()要在不需要並發的時候去調用