併發程式設計系列學習筆記02(Java中的執行緒)

語言: CN / TW / HK

Java執行緒

建立和執行執行緒

  • Thread
    public static void main(String[] args) {
        
        // 建立執行緒物件 lambda
        Thread thread = new Thread(() -> log.info("子執行緒執行"));
        // 設定執行緒名稱
        thread.setName("thread-0000");
        // 啟動執行緒
        thread.start();

        log.info("主執行緒執行完畢");
    }
  • Runnable 配合 Thread
    public static void main(String[] args) {

        Runnable task = new Runnable() {
            @Override
            public void run() {
                log.info("子執行緒已執行");
            }
        };

        Thread thread = new Thread(task, "thread-0001");

        thread.start();

        log.info("主執行緒執行完畢");
    }
  • 使用Runnable小結

    • 將執行緒與任務解耦
    • 從而更容易與執行緒池等高階API配合使用
    • 脫離Thread繼承體系,更靈活
  • FutureTask 配合 Thread

public static void main(String[] args) throws ExecutionException, InterruptedException {

    FutureTask<Integer> futureTask = new FutureTask<>(() -> {
        log.info("FutureTask執行");
        Thread.sleep(1000);
        return 100;
    });

    Thread thread = new Thread(futureTask, "thread-0002");

    thread.start();

    // 獲得返回值,阻塞執行緒未執行完時,將阻塞等待返回結果
    Integer result = futureTask.get();

    log.info("獲得返回值 {} ", result);

    log.info("主執行緒執行完畢");
}

觀察多個執行緒執行情況

  • 交替執行
  • 執行的先後順序不由我們控制

檢視程序與執行緒

  • windows

    • 檢視程序:tasklist
    • 殺死程序:taskkill
  • linux

    • 檢視所有程序 ps -ef
    • 檢視某程序下所有執行緒 ps -fT -p <PID>
    • 殺死程序 kill
    • 顯示程序 top 按 H 切換執行緒
    • top -H -p <PID> 檢視某個PID下所有執行緒
  • Java相關

    • 檢視所有Java 程序 jps
    • 檢視某個Java程序所有執行緒 狀態 jstack <PID>
    • 圖形介面,支援遠端 jconsole

執行緒執行的原理

  • 棧與棧楨

    • JVM主要由堆、棧、方法區組成
    • 堆記憶體其實就是分給一個個執行緒使用的
    • 每個執行緒啟動後,虛擬機器會為其分配一塊棧記憶體
    • 每個棧由多個棧楨(Frame)組成,棧楨對應每次方法呼叫所佔記憶體
    • 一個執行緒同時只能有一個活動棧楨,對應正在執行的那個method
  • 執行緒上下文切換

    • 由於CPU會在不同執行緒間執行切換,切換時,作業系統將儲存當前執行緒狀態,並恢復另一個執行緒狀態

    • 對應Java中的程式計數器概念,記錄下一條JVM指令的執行地址,為執行緒私有

    • 執行緒狀態資訊包括

      • 程式計數器
      • 每個棧楨的資訊
      • 如:區域性變數、運算元棧、返回地址等
    • 頻繁發生執行緒上下文切換影響效能

常見方法

  • start() // 啟動新執行緒,讓執行緒進入就緒狀態
  • run() //執行緒被執行後執行的方法
  • join() // 等待執行緒執行結束
  • join(long n) // 等待結束帶超時時間
  • getId() // 執行緒ID
  • getName() // 執行緒名稱
  • setName(String name) //修改名稱
  • getPriority() // 獲取執行緒執行的優先順序
  • setPriority(int i) // 設定優先順序,1-10,值越大被CPU排程的機率越高
  • getState() // 獲取狀態,一共6個
  • isInterrupted() // 判斷是否被打斷,不會清除打斷標記
  • isAlive() // 判斷執行緒是否存活,還沒執行完畢
  • interrupt() // 打斷執行緒,將丟擲異常,並清除打斷標記(執行緒未在執行)
  • interrupted() // 判斷是否被打斷,同時會清除打斷標記
  • currentThread() // 獲取當前正在執行的執行緒
  • sleep(long n) // 讓執行緒休眠 n 毫秒,讓出CPU時間片
  • yield() // 提示執行緒排程器讓出當前執行緒的CPU,一般用於測試或除錯

start 與 run

  • 直接呼叫run,則會在主執行緒中直接執行run,不會啟動新的執行緒
  • 呼叫 start 才會啟動新執行緒,並通過執行緒間接執行run方法

sleep 與 yield

  • sleep會讓執行緒狀態從Running 進入 Timed Waiting,讓執行緒睡眠,染出CPU的時間片給其他執行緒

  • yield 是 讓執行緒 從Running進入Runnable,主動讓出CPU對執行緒排程,一般用於除錯

  • 執行緒優先順序

    • 僅用於提示排程器優先排程該執行緒,但排程器可能忽略
    • 僅在CPU比較繁忙時才有一定效果,CPU較空閒時幾乎沒有作用

join

  • 用於等待指定執行緒執行完畢
  • 可設定超時時間,超過時間後,將不再等待

interrupt

  • 打斷執行緒執行,並丟擲異常
  • 打斷呼叫了sleep的執行緒,被清空其打斷標記,打斷狀態為false
  • 打斷正在執行的執行緒,不會清空,打斷狀態為true
  • 如果執行緒打斷標記是true,此時呼叫LockSupport.park()暫停執行緒會失效
public static void main(String[] args) {
    Thread t1 = new Thread(() -> {
        Sleeper.sleep(1);
    }, "t1");

    t1.start();

    Sleeper.sleep(0.5);
    // 打斷t1執行緒
    t1.interrupt();
    log.info("打斷執行緒t1後,t1的狀態為 {}", t1.isInterrupted());
}
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at java.lang.Thread.sleep(Thread.java:340)
	at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
	at cn.com.ebidding.test.Sleeper.sleep(Sleeper.java:14)
	at cn.com.ebidding.test.TestInterrupt.lambda$main$0(TestInterrupt.java:15)
	at java.lang.Thread.run(Thread.java:748)
14:37:19.495 [main] INFO cn.com.ebidding.test.TestInterrupt - 打斷執行緒t1後,t1的狀態為 false

幾個不推薦方法

  • stop(停止)、suspend(掛起,暫停)、resume(恢復)
  • 已過時,容易破壞同步程式碼塊,最後造成死鎖

主執行緒與守護執行緒

  • Java程序預設需等待所有執行緒執行結束,程序才會結束
  • 守護執行緒即其他非守護執行緒已結束後,自己會強制結束
  • 設定:t1.setDaemon(true)
  • 垃圾回收執行緒就是守護執行緒
  • Tomcat中的Acceptor 與 Poller 也是,故當Tomcat 執行 shutdown後,不會等待他們處理完當前正在處理的請求

作業系統中的執行緒狀態(五種)

  • 初始:剛建立,還未與作業系統關聯
  • 可執行:與作業系統已關聯,可由CPU排程
  • 執行:已獲取到了CPU時間片,正在執行中;CPU時間片用完,執行中將轉換為可執行,其會導致執行緒上下文切換
  • 阻塞:此時執行緒不會用到CPU,但會導致執行緒上下文切換,例如BIO讀寫檔案場景;注意阻塞與可執行加以區分,阻塞狀態的執行緒只要不喚醒,排程器一直不會考慮排程;
  • 終止:已執行完畢生命週期接結束,不會再轉換為其他狀態

Java JVM中的執行緒狀態(六種)

  • New:剛建立,還未呼叫start
  • Runnable:覆蓋了作業系統中的可執行、執行、阻塞(IO讀寫)
  • Blocked:阻塞狀態的細分
  • Waiting:阻塞狀態的細分
  • Timed_waiting:阻塞狀態的細分
  • Terminated:執行緒程式碼執行結束