Java多線程學習(二)---線程建立方式

線程建立方式

摘要編程

1. 經過繼承Thread類來建立並啓動多線程的方式安全

2. 經過實現Runnable接口來建立並啓動線程的方式多線程

3. 經過實現Callable接口來建立並啓動線程的方式this

4. 總結Java中建立線程的方式,比較各自優點和區別spa

1、繼承Thread類建立線程類

1.1 繼承Thread類建立線程步驟

Java使用Thread類表明線程全部的線程對象都必須是Thread類或其子類實例每一個線程的做用是完成必定的任務,實際上就是執行一段程序流即一段順序執行的代碼。Java使用線程執行體來表明這段程序流。Java中經過繼承Thread類來建立啓動多線程的步驟以下:線程

01. 定義Thread類的子類,並重寫該類的run()方法,該run()方法的方法體就表明了線程須要完成的任務對象

     所以把run()方法稱爲線程執行體blog

02. 建立Thread子類的實例,即建立了線程對象繼承

03. 調用線程對象的start()方法來啓動該線程接口

1.2 繼承Thread類建立線程示例

下面程序示範了經過繼承Thread類來建立並啓動多線程:

  1. // 經過繼承Thread類來建立線程類
  2. public class MyThreadTest extends Thread {
  3.     private int i;
  4.     // 重寫 run方法,run方法的方法體就是線程執行體
  5.     public void run() {
  6.         for (; i < 100; i++) {
  7.             // 當線程類繼承 Thread類時,直接使用this便可獲取當前線程
  8.             // Thread 對象的 getName()返回當前該線程的名字
  9.             // 所以能夠直接調用 getName()方法返回當前線程的名
  10.             System.out.println(getName() + "" + i);
  11.         }
  12.     }
  13.     public static void main(String[] args) {
  14.         for (int i = 0; i < 100; i++) {
  15.             // 調用 ThreadcurrentThread方法獲取當前線程
  16.             System.out.println(Thread.currentThread().getName() + "" + i);
  17.             if (i == 20) {
  18.                 // 建立、並啓動第一條線程
  19.                 new MyThreadTest().start();
  20.                 // 建立、並啓動第二條線程
  21.                 new MyThreadTest().start();
  22.             }
  23.         }
  24.     }
  25. }

運行部分結果:

雖然上面程序只顯式地建立並啓動了2個線程,但實際上程序有3個線程,即程序顯式建立的2個子線程1個主線程。前面已經提到,當Java程序開始運行後,程序至少會建立一個主線程,主線程的線程執行體不是由run()方法肯定的,而是由main()方法肯定的,main()方法的方法體表明主線程的線程執行體。

該程序不管被執行多少次輸出的記錄數是必定的,一共是300條記錄。主線程會執行for循環打印100條記錄,兩個子線程分別打印100條記錄,一共300條記錄。由於i變量是MyThreadTest的實例屬性,而不是局部變量,但由於程序每次建立線程對象時都須要建立一個MyThreadTest對象,因此Thread-0和Thread-1不能共享該實例屬性,因此每一個線程都將執行100次循環。

2、實現Runnable接口建立線程類

2.1 實現Runnable接口建立線程步驟

實現Runnable接口來建立並啓動多線程的步驟以下:

01. 定義Runnable接口的實現類,並重寫該接口的run()方法,該run()方法的方法體一樣是該線程的線程執行體

02. 建立Runnable實現類的實例,並以此實例做爲Thread的target來建立Thread對象,該Thread對象纔是真正的線程對象

03. 調用線程對象的start()方法來啓動線程

須要注意的是:Runnable對象僅僅做爲Thread對象的targetRunnable實現類裏包含的run()方法僅做爲線程執行體。而實際的線程對象依然是Thread實例,只是該Thread線程負責執行其target的run()方法。

2.2實現Runnable接口建立線程示例

下面程序示範了經過實現Runnable接口建立線程步驟:

  1. public class MyRunnableTest implements Runnable {
  2.     private int i;
  3.     void print(){
  4.          System.out.println(Thread.currentThread().getName() + "" + i);
  5.     }
  6.     // run 方法一樣是線程執行體
  7.     public void run() {
  8.         for (; i < 100; i++) {
  9.             // 當線程類實現 Runnable接口時,
  10.             // 若是想獲取當前線程,只能用 Thread.currentThread()方法。
  11.             print();
  12.         }
  13.     }
  14.     public static void main(String[] args) {
  15.         for (int i = 0; i < 100; i++) {
  16.             System.out.println(Thread.currentThread().getName() + "" + i);
  17.             if (i == 20) {
  18.                 MyRunnableTest st = new MyRunnableTest();
  19.                 // 經過 new Thread(target , name)方法建立新線程
  20.                 new Thread(st, " 新線程-1 ").start();
  21.                 new Thread(st, " 新線程-2 ").start();
  22.             }
  23.         }
  24.     }
  25. }

運行部分結果:

從該運行結果中咱們能夠看出,控制檯上輸出的內容是亂序的,並且每次結果不盡相同。這是由於:

01. 在這種方式下,程序所建立的Runnable對象只是線程的target,而多個線程能夠共享同一個target。

02. 因此多個線程能夠共享同一個線程類即線程的target類的實例屬性。

03. 往控制檯窗口print()輸出的過程並非多線程安全的,在一個線程輸出過程當中另外一個線程也能夠輸出。

爲可以保證順序輸出,咱們能夠對打印方法設置Synchronized,讓每次只能有一個進程可以訪問打印,代碼以下:

  1. public class MyRunnableTest implements Runnable {
  2.     private int i;
  3.     synchronized void print(){
  4.          System.out.println(Thread.currentThread().getName() + "" + i);
  5.     }
  6.     // run 方法一樣是線程執行體
  7.     public void run() {
  8.         for (; i < 100; i++) {
  9.             // 當線程類實 Runnable 接口時,
  10.             // 若是想獲取當前線程,只能用 Thread.currentThread()方法。
  11.             print();
  12.         }
  13.     }
  14.     public static void main(String[] args) {
  15.         for (int i = 0; i < 100; i++) {
  16.             System.out.println(Thread.currentThread().getName() + "" + i);
  17.             if (i == 20) {
  18.                 MyRunnableTest st = new MyRunnableTest();
  19.                 // 經過 new Thread(target , name)方法建立新線程
  20.                 new Thread(st, " 新線程-1 ").start();
  21.                 new Thread(st, " 新線程-2 ").start();
  22.             }
  23.         }
  24.     }
  25. }

運行結果:

該運行結果,更加明顯的證實了,在這種方式下,多個線程共享了 tartget類實例 的屬性

3、使用Callable和Future建立線程

3.1 Callable和Future接口概述

3.1.1 callable接口概述

也許受此啓發,從Java 5開始,Java提供了Callable接口,該接口怎麼看都像是Runnable接口的加強版,Callable接口提供了一個call()方法能夠做爲線程執行體,但call()方法比run()方法功能更強大。

01. call()方法能夠有返回值

02. call()方法能夠聲明拋出異常

所以咱們徹底能夠提供一個Callable對象做爲Thread的target,而該線程的線程執行體就是該Callable對象的call()方法。問題是:Callable接口是Java 5新增的接口,並且它不是Runnable接口的子接口,因此Callable對象不能直接做爲Thread的target。並且call()方法還有一 個返回值-----call()方法並非直接調用,它是做爲線程執行體被調用的。那麼如何獲取call()方法的返回值呢?

3.1.2 Future接口概述

Java 5提供了Future接口來表明Callable接口裏call()方法的返回值,併爲Future接口提供了一個FutureTask實現類,該實現類實現了Future接口Runnable接口能夠做爲Thread類的target。在Future接口裏定義了以下幾個公共方法來控制它關聯的Callable任務:

1. boolcan cancel(boolean maylnterruptltRunning):試圖取消該Future裏關聯的Callable任務

2. V get():返回Callable任務裏call()方法的返回值。調用該方法將致使程序阻塞,必須等到子線程結束後纔會獲得返回值

3. V get(long timeout,TimeUnit unit):返回Callable任務裏call()方法的返回值。

    該方法讓程序最多阻塞timeoutunit指定的時間,若是通過指定時間後Callable任務依然沒有返回值,

    將會拋出TimeoutExccption異常

4. boolean isCancelled():若是在Callable任務正常完成前被取消,則返回true

5. boolean isDone():婦果Callable任務已完成,則返回true

注意:Callable接口有泛型限制,Callable接口裏的泛型形參類型與call()方法返回值類型相同。

3.1.3 建立並啓動有返回值的線程的步驟

01. 建立Callable接口的實現類,並實現call()方法,該cal()方法將做爲線程執行體,且該call()方法有返回值

02. 建立Callable實現類的實例,使用FutureTask類來包裝Callable對象

      該FutureTask對象封裝了該Callable對象的call()方法的返回值

03. 使用FutureTask對象做爲Thread對象的target建立並啓動新線程

04. 調用FutureTask對象的get()方法來得到子線程執行結束後的返回值

3.2使用Callable和Future建立線程示例

下面程序示範了經過實現Callable接口建立線程步驟:

  1. public class MyCallableTest implements Callable<Integer>{
  2.     // 實現 call方法,做爲線程執行體
  3.     public Integer call(){
  4.         int i = 0;
  5.         for ( ; i < 100 ; i++ ){
  6.             System.out.println(Thread.currentThread().getName()+ "\t" + i);
  7.         }
  8.         // call() 方法可以有返回值
  9.         return i;
  10.     }
  11.     public static void main(String[] args) {
  12.         // 建立 Callable對象
  13.         MyCallableTest myCallableTest = new MyCallableTest();
  14.         // 使用 FutureTask來包裝Callable對象
  15.         FutureTask<Integer> task = new FutureTask<Integer>(myCallableTest);
  16.         for (int i = 0 ; i < 100 ; i++){
  17.             System.out.println(Thread.currentThread().getName()+ " \t" + i);
  18.             if (i == 20){
  19.                 // 實質仍是以 Callable對象來建立、並啓動線程
  20.                 new Thread(task , "callable").start();
  21.             }
  22.         }
  23.         try{
  24.             // 獲取線程返回值
  25.             System.out.println("callable 返回值: " + task.get());
  26.         }
  27.         catch (Exception ex){
  28.             ex.printStackTrace();
  29.         }
  30.     }
  31. }

運行上面程序,將看到主線程和call()方法所表明的線程交替執行的情形,程序最後還會輸出call()方法的返回值:

上面程序中建立Callable實現類與建立Runnable實現類並無太大的差異,只是Callable的call()方法容許聲明拋出異常, 並且容許帶返回值。當主線程中當循環變量i等於20時,程序啓動以FutureTask對象爲target的線程。程序最後調用FutureTask對象 的get()方法來返回call()方法的返回值——該方法將致使主線程阻塞,直到call()方法結束並返回爲止。

4、總結

經過如下三種途徑能夠實現多線程:

01. 繼承Thread類

02. 實現Runnable接口

03. 實現Callable接口

不過實現Runnable接口與實現Callable接口的方式基本相同,只是Callable接口裏定義的方法有返回值,能夠聲明拋出異常而已。 所以能夠將實現Runnable接口和實現Callable接口歸爲一種方式。這種方式與繼承Thread方式之間的主要差異以下。

(1) 採用實現Runnable、Callable接口的方式建立多線程

線程類只是實現了Runnable接口或Callable接口,還能夠繼承其餘類。

在這種方式下,多個線程能夠共享同一個target對象,因此很是適合多個相同線程來處理同一份資源的狀況,從而能夠將CPU、代碼和數據分開,造成清晰的模型,較好地體現了面向對象的思想。

劣勢:編程稍稍複雜,若是須要訪問當前線程,則必須使用Thread.currentThread()方法。

(2) 採用繼承Thread類的方式建立多線程

劣勢:由於線程類已經繼承了Thread類,因此不能再繼承其餘父類

若是,您認爲閱讀這篇博客讓您有些收穫,不妨點擊一下右下角的【推薦】。

若是,您但願更容易地發現個人新博客,不妨點擊一下左下角的【關注我】。

若是,您對個人博客所講述的內容有興趣,請繼續關注個人後續博客,我是【Sunddenly】。

本文版權歸做者和博客園共有,歡迎轉載,但未經做者贊成必須保留此段聲明,且在文章頁面明顯位置給出原文鏈接,不然保留追究法律責任的權利

相關文章
相關標籤/搜索