最全java多線程學習總結1--線程基礎


  《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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM