天天看點

java多線程——線程的建立

java多線程——線程的建立

1、建立線程方式一:繼承Thread類

(1)定義一個類繼承Thread;

(2)重寫run方法;

(3)建立線程對象;

(4)調用start方法,開啟線程并讓線程執行,同時還會告訴JVM調用run方法。

1 class Demo extends Thread{
 2     private String name;
 3     Demo(String name){
 4         this.name=name;
 5     }
 6     @Override
 7     public void run() {
 8         for(int i=1;i<=20;i++) {
 9             System.out.println("name="+name+"......."+i);
10         }
11     }
12 }
13 
14 public class ThreadDemo {
15     public static void main(String[] args) {
16         //method0();
17         //method1();
18         Demo d1=new Demo("小強");
19         Demo d2=new Demo("旺财");
20         d2.start();//開啟多一個執行路徑
21         d1.run();//由主線程負責
22     }
23 
24     public static void method1() {
25         Demo d1=new Demo("小強");
26         Demo d2=new Demo("旺财");
27         d1.run();//由主線程負責
28         d2.start();//開啟多一個執行路徑,主線程結束才開啟
29     }
30 
31     public static void method0() {
32         Demo d1=new Demo("小強");
33         Demo d2=new Demo("旺财");
34         d1.run();//由主線程負責
35         d2.run();//由主線程負責
36     }        
37 }      

ThreadDemo

以上代碼中,method1和method0運作效果相同,但是method1确實開啟了多線程,隻是在主線程結束後才開啟。

線程對象調用run方法和調用start方法的差別?

調用run方法不開啟線程,僅是對象調用方法;調用start開啟線程,并讓JVM調用run方法在開啟的線程中執行。

為什麼要繼承Thread類并重寫run方法?

因為Thread類描述的是線程事物,具備線程該有的功能。既然如此?為什麼不能直接建立Thread類的對象:Thread t1=new Thread(); t1.start();這種寫法沒有錯,但是該start方法調用的是Thread類中的run方法,而這個run方法沒有任何操作,更重要的是,這個run方法中沒有定義我們需要讓線程執行的代碼。建立線程的目的是為了建立單獨的路徑,讓多部分代碼實作同時執行。也就是說,線程建立并執行需要給定的代碼(線程的任務)。對于我們常用的主線程,它的任務都定義在main函數中。自定義線程需要執行的任務都定義在run方法中。Thread類中的run方法内部的任務不是我們所需要,是以需要進行run方法的重寫。

記憶體占用:多線程執行時,在棧記憶體中,其實每一個執行線程都有一片自己所屬的棧記憶體空間,進行方法的壓棧和彈棧。當執行線程的任務結束了,線程自動在棧記憶體中釋放了。當所有的執行線程都結束了,程序就結束了。

2、建立線程方式二:實作Runnable接口

(1)聲明一個類實作

Runnable

接口(避免了繼承Thread類的單繼承局限性)。

(2)該類實作

run

方法(将線程任務代碼定義到run方法中)。

(3)建立Thread類的對象(隻有建立Thread類的對象才能建立線程)。

(4)将Runnable接口的子類對象作為參數傳遞給Thread類的構造函數。

(5)啟動線程。

1 //)聲明一個類實作Runnable接口,該類實作run方法。
 2 class Demo2 implements Runnable{
 3     private String name;
 4     Demo2(String name){
 5         this.name=name;
 6     }
 7     @Override
 8     public void run() {
 9         for(int i=1;i<=20;i++) {
10             System.out.println("name="+name+"....."+Thread.currentThread().getName()+"...."+i);
11         }
12     }
13 }
14 
15 public class ThreadDemo2 {
16     public static void main(String[] args) {
17         //(2)建立Runnable子類的對象
18         Demo2 d1=new Demo2("小強");
19         Demo2 d2=new Demo2("旺财");
20         //(3)建立Thread類的對象,将Runnable接口的子類對象作為參數傳遞給Thread類的構造函數。
21         Thread t1=new Thread(d1);
22         Thread t2=new Thread(d2);
23         //(4)啟動線程。
24         t1.start();
25         t2.start();
26         for(int i=0;i<20;i++) {
27             System.out.println(Thread.currentThread().getName()+"----->"+i);
28         }
29         
30     }
31 }      

ThreadDemo2

在Thread類中相關源碼抽離出來如下:

1 public class Thread {
 2     private Runnable target;
 3     public Thread(Runnable target){
 4         this.target=target;
 5     }
 6     
 7     public void run() {
 8         if (target != null) {
 9             target.run();
10         }
11     }    
12 }      

優點(避免單繼承+解耦):

第二種方式實作Runnable接口避免了單繼承的局限性,是以較為常用。

實作Runnable接口的方式,更加符合面向對象,線程分為兩部分,一部分線程對象,一部分線程任務。繼承Thread類,線程對象和線程任務耦合在一起。一旦建立Thread類的子類對象,既有線程對象,又有線程任務。而Runnable接口,将線程任務單獨分離出來,封裝成對象,類型就是Runnable接口類型。

3、建立線程方式三:使用Callable和Future建立線程

和Runnable接口不一樣,Callable接口提供了一個call()方法作為線程執行體,call()方法比run()方法功能要強大:call()方法可以有傳回值,且call()方法可以聲明抛出異常。

(1)建立Callable接口的實作類,并實作call()方法,該call()方法将作為線程執行體,且該call()方法沒有傳回值,再建立Callable實作類的執行個體。(從java8開始,可以直接使用Lambda表達式建立Callable對象)。

(2)使用FutureTask類來包裝Callable對象,該FutureTask對象封裝了該Callable對象的call()方法的傳回值。

(3)使用FutureTask作為Thread對象的target建立并啟動新線程。

(4)調用FutureTask對象的get方法來獲得子線程執行結束後的傳回值。

1 import java.util.concurrent.Callable;
 2 import java.util.concurrent.ExecutionException;
 3 import java.util.concurrent.FutureTask;
 4 
 5 class DemoCall implements Callable{
 6     private int n;
 7     @Override
 8     public Object call() throws Exception {
 9         int i=1;
10         while(i++<n) {
11             System.out.println(Thread.currentThread().getName()+"-------->"+i);
12         }
13         return Thread.currentThread().getName()+"運作完畢----------n="+n;
14     }
15     
16     public DemoCall(int n) {
17         this.n=n;
18     }
19 }
20 class CallableDemo {
21     public static void main(String[] args) {
22         DemoCall d1=new DemoCall(20);
23         DemoCall d2=new DemoCall(50);
24         // 使用Callable方式建立線程,需要FutureTask類的支援,用于接收運算結果,可以使用泛型指定傳回值的類型
25         FutureTask<String> ft1=new FutureTask<String>(d1);
26         FutureTask<String> ft2=new FutureTask<String>(d2);
27         new Thread(ft1).start();
28         new Thread(ft2).start();
29          // 接收運算結果
30         // 隻有當該線程執行完畢後才會擷取到運算結果,等同于閉鎖的效果
31         try {
32             System.out.println(ft1.get());
33             System.out.println(ft2.get());
34         } catch (InterruptedException e) {
35             // TODO Auto-generated catch block
36             e.printStackTrace();
37         } catch (ExecutionException e) {
38             // TODO Auto-generated catch block
39             e.printStackTrace();
40         }
41         
42     }
43 }      

CallableDemo

java多線程——線程的建立