Java中建立執行緒的三種方式以及區別
在Java中如果要建立執行緒的話,一般有3種方法:
- 繼承Thread類;
- 實現Runnable介面;
- 使用Callable和Future建立執行緒。
1. 繼承Thread類
繼承Thread類的話,必須重寫run方法,在run方法中定義需要執行的任務。
class MyThread extends Thread{
private static int num = 0;
public MyThread(){
num++;
}
@Override
public void run() {
System.out.println("主動建立的第"+num+"個執行緒");
}
}
建立好了自己的執行緒類之後,就可以建立執行緒物件了,然後通過start()方法去啟動執行緒。注意,不是呼叫run()方法啟動執行緒,run方法中只是定義需要執行的任務,如果呼叫run方法,即相當於在主執行緒中執行run方法,跟普通的方法呼叫沒有任何區別,此時並不會建立一個新的執行緒來執行定義的任務。
public class Test {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start();
}
}
class MyThread extends Thread{
private static int num = 0;
public MyThread(){
num++;
}
@Override
public void run() {
System.out.println("主動建立的第"+num+"個執行緒");
}
}
在上面程式碼中,通過呼叫start()方法,就會建立一個新的執行緒了。為了分清start()方法呼叫和run()方法呼叫的區別,請看下面一個例子:
public class Test {
public static void main(String[] args) {
System.out.println("主執行緒ID:"+Thread.currentThread().getId());
MyThread thread1 = new MyThread("thread1");
thread1.start();
MyThread thread2 = new MyThread("thread2");
thread2.run();
}
}
class MyThread extends Thread{
private String name;
public MyThread(String name){
this.name = name;
}
@Override
public void run() {
System.out.println("name:"+name+" 子執行緒ID:"+Thread.currentThread().getId());
}
}
執行結果:
從輸出結果可以得出以下結論:
1)thread1和thread2的執行緒ID不同,thread2和主執行緒ID相同,說明通過run方法呼叫並不會建立新的執行緒,而是在主執行緒中直接執行run方法,跟普通的方法呼叫沒有任何區別;
2)雖然thread1的start方法呼叫在thread2的run方法前面呼叫,但是先輸出的是thread2的run方法呼叫的相關資訊,說明新執行緒建立的過程不會阻塞主執行緒的後續執行。
2. 實現Runnable介面
在Java中建立執行緒除了繼承Thread類之外,還可以通過實現Runnable介面來實現類似的功能。實現Runnable介面必須重寫其run方法。
下面是一個例子:
public class Test {
public static void main(String[] args) {
System.out.println("主執行緒ID:"+Thread.currentThread().getId());
MyRunnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);
thread.start();
}
}
class MyRunnable implements Runnable{
public MyRunnable() {
}
@Override
public void run() {
System.out.println("子執行緒ID:"+Thread.currentThread().getId());
}
}
Runnable的中文意思是“任務”,顧名思義,通過實現Runnable介面,我們定義了一個子任務,然後將子任務交由Thread去執行。注意,這種方式必須將Runnable作為Thread類的引數,然後通過Thread的start方法來建立一個新執行緒來執行該子任務。如果呼叫Runnable的run方法的話,是不會建立新執行緒的,這根普通的方法呼叫沒有任何區別。
事實上,檢視Thread類的實現原始碼會發現Thread類是實現了Runnable介面的。
在Java中,這2種方式都可以用來建立執行緒去執行子任務,具體選擇哪一種方式要看自己的需求。直接繼承Thread類的話,可能比實現Runnable介面看起來更加簡潔,但是由於Java只允許單繼承,所以如果自定義類需要繼承其他類,則只能選擇實現Runnable介面。
3. 使用Callable和Future建立執行緒
和Runnable介面不一樣,Callable介面提供了一個call()方法作為執行緒執行體,call()方法比run()方法功能要強大。
建立並啟動有返回值的執行緒的步驟如下:
- 建立Callable介面的實現類,並實現call()方法,然後建立該實現類的例項(從java8開始可以直接使用Lambda表示式建立Callable物件)。
- 使用FutureTask類來包裝Callable物件,該FutureTask物件封裝了Callable物件的call()方法的返回值
- 使用FutureTask物件作為Thread物件的target建立並啟動執行緒(因為FutureTask實現了Runnable介面)
- 呼叫FutureTask物件的get()方法來獲得子執行緒執行結束後的返回值
下面是一個例子:
public class Main {
public static void main(String[] args){
MyThread3 th=new MyThread3();
//使用Lambda表示式建立Callable物件
//使用FutureTask類來包裝Callable物件
FutureTask<Integer> future=new FutureTask<Integer>(
(Callable<Integer>)()->{
return 5;
}
);
new Thread(task,"有返回值的執行緒").start();//實質上還是以Callable物件來建立並啟動執行緒
try{
System.out.println("子執行緒的返回值:"+future.get());//get()方法會阻塞,直到子執行緒執行結束才返回
}catch(Exception e){
ex.printStackTrace();
}
}
}
三種建立執行緒方式對比:
實現Runnable和實現Callable介面的方式基本相同,不過是後者執行call()方法有返回值,後者執行緒執行體run()方法無返回值,因此可以把這兩種方式歸為一種這種方式與繼承Thread類的方法之間的差別如下:
- 執行緒只是實現Runnable或實現Callable介面,還可以繼承其他類。
- 這種方式下,多個執行緒可以共享一個target物件,非常適合多執行緒處理同一份資源的情形。
- 但是程式設計稍微複雜,如果需要訪問當前執行緒,必須呼叫Thread.currentThread()方法。
- 繼承Thread類的執行緒類不能再繼承其他父類(Java單繼承決定)。
PS:一般推薦採用實現介面的方式來建立多執行緒
Linux公社的RSS地址 : https://www.linuxidc.com/rssFeed.aspx
本文永久更新連結地址: https://www.linuxidc.com/Linux/2019-02/156774.htm