最全java多執行緒學習總結1--執行緒基礎

煩囂的人發表於2019-06-28

  《java 核心技術》這本書真的不錯,知識點很全面,翻譯質量也還不錯,本系列博文是對該書中併發章節的一個總結。

什麼是執行緒

  官方解釋:執行緒是作業系統能夠進行運算排程的最小單位,包含於程式之中,是程式中的實際運作單位。也就是說執行緒是程式碼執行的載體,我們所編寫的程式碼都是線上程上跑的,以一個最簡單的 hellowWorld 為例:

public class Main {

    public static void main(String[] args) {
        System.out.println("Hello World!");
        System.out.println("當前執行緒名為:"+Thread.currentThread().getName());
        System.out.println("當前執行緒id為:"+Thread.currentThread().getId());
    }
}

結果為:

Hello World!
當前執行緒名為:main
當前執行緒id為:1

在程式執行時預設會建立一個主執行緒來執行程式碼,執行緒名為:main,執行緒 id 為 1

什麼是多執行緒

  顧名思義就是多個執行緒同時執行,提高程式執行速度。單個執行緒一次只能做一件事,想要提高執行效率有兩種途徑:

  • 非同步。因為大多數時候執行緒都不是時刻在進行計算,都是在等待 io 操作,那麼就可以將等待時間利用起來以提高執行緒的利用率。這裡不做過多討論,想要進一步瞭解非同步的可以學習 Node.js(原生支援非同步)
  • 多執行緒。一個執行緒一次只能做一件事,那麼多個執行緒就能同時做多件事了,通過增大執行緒數來提高執行速度。

如何建立執行緒

  建立執行緒有兩種方法

  • 繼承 Thread 類
  • 實現 runnable 介面

繼承 Thread 類

  不推薦本方式來建立執行緒,原因顯而易見:java 不支援多繼承,如果繼承了 Thread 類就不能再繼承其他類了。

  使用繼承方式建立執行緒程式碼如下:

public class CustomThreadExtendThread extends Thread{

    @Override
    public void run() {
        String threadName = Thread.currentThread().getName();
        long threadId = Thread.currentThread().getId();
        System.out.println("建立執行緒名為:"+threadName+",id為:"+threadId);
    }

    public static void main(String[] args){
        Thread thread1 = new CustomThreadExtendThread();
        Thread thread2 = new CustomThreadExtendThread();
        thread1.start();
        thread2.start();
    }
}

實現 runnable 介面

  實現介面來建立執行緒是目前推薦的一種方式,原因也很簡單:一個類可以實現多個介面。實現 Runnable 介面並不影響實現類再去實現其他介面。

  使用實現介面方式建立執行緒程式碼如下:

public class CustomThreadImplementInterface implements Runnable {
    @Override
    public void run() {
        Thread.currentThread().setName(((Double) Math.random()).toString());
        String threadName = Thread.currentThread().getName();
        long threadId = Thread.currentThread().getId();
        System.out.println("建立執行緒名為:" + threadName + ",id為:" + threadId);
    }

    public static void main(String[] args) {
        Thread thread1 = new Thread(new CustomThreadImplementInterface());
        Thread thread2 = new Thread(new CustomThreadExtendThread());
        thread1.start();
        thread2.start();

        //使用lambda表示式,讓建立執行緒更簡單
        new Thread(() -> {
            System.out.println("建立了一個新執行緒");
        }).start();
    }
}

  通過檢視 Thread 原始碼可以看到 Thread 類也是 Runnable 介面的一個實現類。

PS:後續程式碼全部使用 runnable 建立執行緒

執行緒狀態

  上面只是演示了執行緒的建立,現在來詳細瞭解執行緒的狀態。在 java 規範中,執行緒可以有以下 6 種狀態:

  • New(新建立)
  • Runnable(可執行)
  • Blocked(阻塞)
  • Waiting(等待)
  • Timed waiting(計時等待)
  • Terminated(被終止)

新建立執行緒

  當使用 new 操作符建立一個執行緒時,如 new Thread(r),執行緒還未開始執行,就屬於新建立狀態。

可執行執行緒

  一旦呼叫 Thread 類的 start 方法,執行緒就處於可執行狀態。

為什麼要叫執行狀態?

  因為 Java 的規範中並沒有將正在 CPU 上執行定義為一個單獨的狀態。因此處於可執行狀態的執行緒可能正在執行,也可能沒有執行,取決於 CPU 的排程策略。

被阻塞執行緒和等待執行緒

  當執行緒處於阻塞或等待狀態時,不執行任何程式碼且消耗最少的資源。直到重新執行。有如下幾種途徑讓執行緒進入阻塞或等待狀態:

  • 當一個執行緒試圖獲取一個內部的物件鎖,而該鎖被其他執行緒持有
  • 當執行緒等待另一個執行緒通知排程器一個條件時,進入等待狀態。比如呼叫 Object.wait 或 Thread.join 方法,或等待 java.util.concurrent 庫中的 Lock 或 Condition 時。
  • 當呼叫計時等待方法時。比如 Thread.sleep,Object.wait,Thread.join,Lock.tryLock 以及 Condition.await

被終止的執行緒

  執行緒可由以下兩種辦法進入終止狀態:

  • run 方法的結束而自然死亡
  • 未捕獲異常中止了 run 方法而意外死亡

注意: 呼叫執行緒的 stop 方法也可以終止執行緒,但是這個方法已經被棄用,最好不要使用。

執行緒屬性

  執行緒有各種屬性:優先順序,守護執行緒,執行緒組以及處理未捕獲異常處理器。

執行緒優先順序

  java 中,每個執行緒都有一個優先順序。預設情況下,執行緒繼承父執行緒優先順序。也可以呼叫setPriority方法指定優先順序。優先順序範圍:1(MIN_PRIORITY)-10(MAX_PRIORITY).NORM_PRIORITY 為 5,這些常量定義在 Thread 類中.

注意: 執行緒優先順序時高度依賴於系統的,因此當 java 執行緒優先順序對映到宿主機平臺的優先順序時,優先順序個數可能會變少或者變成 0.比如,Windows 中有 7 個優先順序,java 執行緒對映時部分優先順序將會對映到相同的作業系統優先順序上。Oracle 為 Linux 編寫的 java 虛擬機器中,忽略了執行緒的優先順序,所有 java 執行緒都有相同的優先順序。不要編寫依賴優先順序的程式碼

守護執行緒

  通過呼叫Thread.setDaemon(true)將一個執行緒轉換為守護執行緒。守護執行緒唯一的使用者是為其他執行緒提供服務,比如計時執行緒,定時傳送計時訊號給其他執行緒。因此當虛擬機器中只有守護執行緒時,虛擬機器就會關閉退出。不要在守護執行緒中訪問任何資源,處理任何業務邏輯

未捕獲異常處理器

  執行緒的 run 方法不能丟擲任何受查異常,非受查異常會導致執行緒終止,除了 try/catch 捕獲異常外,還可以通過未捕獲異常處理器來處理異常。異常處理器需要實現Thread.UncaughtExceptionHandler介面。

  可以使用執行緒示例的setUncaughtExceptionHandler()方法為某個執行緒設定處理器,也可使用Thread.setDefaultUncaughtExceptionHandler()為所有執行緒設定預設處理器,程式碼如下:

public class CustomExceptionHandler implements Thread.UncaughtExceptionHandler {

    @Override
    public void uncaughtException(Thread t, Throwable e) {
        System.out.println("捕獲到執行緒"+t.getName()+",異常:" + e.getMessage());
        e.printStackTrace();
    }

    public static void main(String[] args) {
        Thread.setDefaultUncaughtExceptionHandler(new CustomExceptionHandler());
        new Thread(() -> {
            throw new RuntimeException("test");
        }).start();
    }
}

  如果不設定預設處理器且不為獨立的執行緒設定處理器,那麼該執行緒的處理器就為該執行緒的執行緒組物件--ThreadGroup(因為執行緒組物件實現了Thread.UncaughtExceptionHandler介面)。

本篇所用全部程式碼:github

本篇原創釋出於:https://www.tapme.top/blog/detail/2019-04-08-20-52

相關文章