Java基礎系列:多線程(一)線程概念與創建

Java 多線程編程

Java 給多線程編程提供了內置的支持。 一條線程指的是進程中一個單一順序的控制流,一個進程中可以併發多個線程,每條線程並行執行不同的任務。

用多線程只有一個目的,那就是更好的利用cpu的資源,因爲大部分的多線程代碼都可以用單線程來實現。

多線程是多任務的一種特別的形式,但多線程使用了更小的資源開銷。

這裏定義和線程相關的一些術語 :

  • 進程:一個進程包括由操作系統分配的內存空間,包含一個或多個線程。一個線程不能獨立的存在,它必須是進程的一部分。一個進程一直運行,直到所有的非守護線程都結束運行後才能結束。
  • 並行與併發
    • 並行:多個cpu實例或者多臺機器同時執行一段處理邏輯,是真正的同時。
    • 併發:通過cpu調度算法,讓用戶看上去同時執行,實際上從cpu操作層面不是真正的同時。併發往往在場景中有公用的資源,那麼針對這個公用的資源往往產生瓶頸,我們會用TPS或者QPS來反應這個系統的處理能力。
  • 線程安全:指在併發的情況之下,代碼經過多線程使用,線程的調度順序不影響任何結果。這個時候使用多線程,我們只需要關注系統的內存,cpu是不是夠用即可。反過來,線程不安全就意味着線程的調度順序會影響最終結果。
  • 同步:Java中的同步指的是通過人爲的控制和調度,保證共享資源的多線程訪問成爲線程安全,來保證結果的準確。如上面的代碼簡單加入@synchronized關鍵字。在保證結果準確的同時,提高性能,纔是優秀的程序。線程安全的優先級高於性能。

多線程能滿足程序員編寫高效率的程序來達到充分利用 CPU 的目的。

線程的生命週期

線程是一個動態執行的過程,它也有一個從產生到死亡的過程。

下圖顯示了一個線程完整的生命週期。
在這裏插入圖片描述

  • 新建狀態:
    使用 new 關鍵字和 Thread 類或其子類建立一個線程對象後,該線程對象就處於新建狀態。它保持這個狀態直到程序 start() 這個線程。

  • 就緒狀態:
    當線程對象調用了start()方法之後,該線程就進入就緒狀態。就緒狀態的線程處於就緒隊列中,要等待JVM裏線程調度器的調度。

  • 運行狀態:
    如果就緒狀態的線程獲取 CPU 資源,就可以執行 run(),此時線程便處於運行狀態。處於運行狀態的線程最爲複雜,它可以變爲阻塞狀態、就緒狀態和死亡狀態

  • 阻塞狀態:
    如果一個線程執行了sleep(睡眠)、suspend(掛起)等方法,失去所佔用資源之後,該線程就從運行狀態進入阻塞狀態。在睡眠時間已到或獲得設備資源後可以重新進入就緒狀態。可以分爲三種:

    • 等待阻塞:運行狀態中的線程執行 wait() 方法,使線程進入到等待阻塞狀態。

    • 同步阻塞:線程在獲取 synchronized 同步鎖失敗(因爲同步鎖被其他線程佔用)。

    • 其他阻塞:通過調用線程的 sleep() 或 join() 發出了 I/O 請求時,線程就會進入到阻塞狀態。當sleep() 狀態超時,join() 等待線程終止或超時,或者 I/O 處理完畢,線程重新轉入就緒狀態。

  • 死亡狀態:
    一個運行狀態的線程完成任務或者其他終止條件發生時,該線程就切換到終止狀態。

線程的優先級

每一個 Java 線程都有一個優先級,這樣有助於操作系統確定線程的調度順序。

Java 線程的優先級是一個整數,其取值範圍是 1 (Thread.MIN_PRIORITY ) - 10 (Thread.MAX_PRIORITY )。

默認情況下,每一個線程都會分配一個優先級 NORM_PRIORITY(5)。

具有較高優先級的線程對程序更重要,並且應該在低優先級的線程之前分配處理器資源。但是,線程優先級不能保證線程執行的順序,而且非常依賴於平臺

創建一個線程

Java 提供了三種創建線程的方法:

  • 通過實現 Runnable 接口;
  • 通過繼承 Thread 類本身;
  • 通過 Callable 和 Future 創建線程。
通過實現 Runnable 接口來創建線程

通過類實現Runnable接口並重寫run方法,利用Thread線程類的帶參數構造方法實例化線程對象,通過調用線程對象的start方法啓動線程。

public class MyRunnable implements Runnable {

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }

    public static void main(String[] args) {
        Thread thread = new Thread(new MyRunnable());
        thread.start();
    }
}
通過繼承 Thread 來創建線程

直接繼承Thread類並重寫run方法,實例化當前類對象並通過調用線程對象的start方法啓動線程。其本質上也是實現了 Runnable 接口的一個實例。

public class MyThread extends Thread {

    @Override
    public void run() {
        System.out.println(this.getName());
    }

    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start();
    }
}
通過 Callable 和 Future 創建線程
  1. 創建 Callable 接口的實現類,並實現 call() 方法,該 call() 方法將作爲線程執行體,並且有返回值。

  2. 創建 Callable 實現類的實例,使用 FutureTask 類來包裝 Callable 對象,該 FutureTask 對象封裝了該 Callable 對象的 call() 方法的返回值。

  3. 使用 FutureTask 對象作爲 Thread 對象的 target 創建並啓動新線程。

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

public class CallableTest implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        int i = 0;
        for (; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
        }
        //睡眠2s
        TimeUnit.SECONDS.sleep(2);
        return i;
    }

    public static void main(String[] args) {
        FutureTask<Integer> task = new FutureTask<>(new CallableTest());
        Thread t1 = new Thread(task);
        t1.start();
        try {
            //獲取返回值將阻塞值線程完成
            System.out.println(task.get());
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }
}

我們發現FutureTask類實際上是同時實現了Runnable和Future接口,由此才使得其具有Future和Runnable雙重特性。通過Runnable特性,可以作爲Thread對象的target,而Future特性,使得其可以取得新創建線程中的call()方法的返回值。
上述主要講解了三種常見的線程創建方式,對於線程的啓動而言,都是調用線程對象的start()方法,需要特別注意的是:不能對同一線程對象兩次調用start()方法

創建線程的三種方式的對比

  1. 採用實現 Runnable、Callable 接口的方式創建多線程時,線程類只是實現了 Runnable 接口或 Callable 接口,還可以繼承其他類。

  2. 使用繼承 Thread 類的方式創建多線程時,編寫簡單,如果需要訪問當前線程,則無需使用 Thread.currentThread() 方法,直接使用 this 即可獲得當前線程。

多線程的使用

有效利用多線程的關鍵是理解程序是併發執行而不是串行執行的。例如:程序中有兩個子系統需要併發執行,這時候就需要利用多線程編程。

通過對多線程的使用,可以編寫出非常高效的程序。不過請注意,如果你創建太多的線程,程序執行的效率實際上是降低了,而不是提升了

請記住,上下文的切換開銷也很重要,如果你創建了太多的線程,CPU 花費在上下文的切換的時間將多於執行程序的時間!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章