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