Java併發編程之線程狀態

個人博客請訪問 http://www.x0100.top            

1. 創建啓動線程

兩種方法

創建和啓動線程兩種方法:繼承 Thread 類、實現 Runable 接口。

方法一:繼承 Thread

public class Test {
    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start();
    }
}

class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("創建線程");
    }
}

方法二:實現 Runable

public class Test {
    public static void main(String[] args)  {
        Thread thread = new Thread(new MyRunnable());
        thread.start();
    }
}

class MyRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println("創建線程");
    }
}

創建線程實現

通過查看 Thread 類代碼,不管是繼承 Thread 類創建線程還是實現 Runable 創建線程,都是調用 Thread 類的 init 方法,來看下 init 方法:

private void init(ThreadGroup g, Runnable target, String name,
        long stackSize, AccessControlContext acc,
        boolean inheritThreadLocals) {
    if (name == null) {
        throw new NullPointerException("name cannot be null");
    }

    this.name = name;

    // 當前線程就是此新線程的父線程
    Thread parent = currentThread();
    SecurityManager security = System.getSecurityManager();
    if (g == null) {
        if (security != null) {
            g = security.getThreadGroup();
        }
        if (g == null) {
            g = parent.getThreadGroup();
        }
    }

    g.checkAccess();

    if (security != null) {
        if (isCCLOverridden(getClass())) {
            security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
        }
    }

    g.addUnstarted();

    // 新線程繼承了parent線程的group、是否爲Daemon、優先級priority、加載資源的contextClassLoader、可繼承的ThreadLocal。
    this.group = g;
    this.daemon = parent.isDaemon();
    this.priority = parent.getPriority();
    if (security == null || isCCLOverridden(parent.getClass()))
        this.contextClassLoader = parent.getContextClassLoader();
    else
        this.contextClassLoader = parent.contextClassLoader;
    this.inheritedAccessControlContext = acc != null ? acc
            : AccessController.getContext();
    this.target = target;
    setPriority(priority);
    if (inheritThreadLocals && parent.inheritableThreadLocals != null)
        this.inheritableThreadLocals = ThreadLocal
                .createInheritedMap(parent.inheritableThreadLocals);

    this.stackSize = stackSize;

    // parent線程爲新線程分配一個唯一的ID
    tid = nextThreadID();
}

通過 init 方法可以看出:

  1. 新構造的線程對象是由其 parent 線程來進行空間分配的。

  2. 新線程繼承了 parent 線程的 group、是否爲 Daemon、優先級 priority、加載資源的 contextClassLoader、可繼承的 ThreadLocal。

  3. parent 線程會分配一個唯一的 ID 來標識這個 child 新線程。

啓動線程

start()方法啓動線程。

線程 start()方法的含義是:當前線程(即 parent 線程)同步告知 Java 虛擬機,只要線程規劃器空閒,應立即啓動調用 start()方法的線程。

注意:

  1. 啓動線程調用 start()方法,而不是 run()。

  2. 啓動一個線程前,最好爲這個線程設置線程名稱,因爲這樣在使用 jstack 分析程序或者進行問題排查時,就會給開發人員提供一些提示,自定義的線程最好能夠起個名字。

2. 線程狀態

狀態

1. 新建狀態(NEW)

當程序使用 new 關鍵字創建了一個線程之後,線程就處於新建狀態,此時的線程情況如下:

  1. 此時 JVM 爲其分配內存,並初始化其成員變量的值;

  2. 此時線程對象沒有表現出任何線程的動態特徵,程序也不會執行線程的線程執行體;

2. 就緒狀態(RUNNABLE)

當線程對象調用了 start()方法之後,線程處於就緒狀態。此時的線程情況如下:

  1. 此時 JVM 會爲其創建方法調用棧和程序計數器;

  2. 線程並沒有開始運行,而是等待系統爲其分配 CPU 時間片;

3. 運行狀態(RUNNING)

當線程獲得了 CPU 時間片,CPU 調度處於就緒狀態的線程並執行 run()方法的線程執行體,則該線程處於運行狀態。

如果計算機只有一個CPU,那麼在任何時刻只有一個線程處於運行狀態;
如果在一個多處理器的機器上,將會有多個線程並行執行,處於運行狀態;
當線程數大於處理器數時,依然會存在多個線程在同一個CPU上輪換的現象;

對於採用搶佔式策略的系統而言,系統會給每個可執行的線程分配一個時間片來處理任務;當該時間片用完後,系統就會剝奪該線程所佔用的資源,讓其他線程獲得執行的機會。此時線程就會又從運行狀態變爲就緒狀態,重新等待系統分配資源。

4. 阻塞狀態(BLOCKED)

處於運行狀態的線程在某些情況下,讓出 CPU 並暫時停止自己的運行,進入阻塞狀態。如:線程阻塞於 synchronized 鎖。

5. 等待狀態(WAITING)

線程處於無限制等待狀態,等待一個特殊的事件來重新喚醒,喚醒線程之後進入就緒狀態,如:

通過wait()方法進行等待的線程等待一個notify()或者notifyAll()方法;
通過join()方法進行等待的線程等待目標線程運行結束而喚醒;

阻塞在 java.concurrent 包中 Lock 接口的線程狀態不是 BLOCK 狀態,而是 WAITING 等待狀態,因爲 java.concurrent 包中 Lock 接口對於阻塞的實現均使用了 LockSupport 類中的相關方法。

6. 超時等待狀態(TIMED_WAITING)

線程進入了一個時限等待狀態,如:sleep(3000),等待 3 秒後線程重新進入就緒狀態。

7. 死亡狀態(DEAD)

線程會以如下 3 種方式結束,結束後就處於死亡狀態:

① run()或 call()方法執行完成,線程正常結束;

② 線程拋出一個未捕獲的 Exception 或 Error;

③ 直接調用該線程 stop()方法來結束該線程—該方法容易導致死鎖,通常不推薦使用;

狀態轉換

線程狀態轉換——《Java併發編程藝術》

查看線程狀態

Java 服務器問題排查中,經常需要查看線程狀態來定位問題。

查看如下示例代碼的線程運行狀態:

public class ThreadState {
    public static void main(String[] args) {
        new Thread(new RunThread(), "RunThread").start();// 一直處於RUNNABLE線程
        new Thread(new TimeWaiting(), "TimeWaitingThread").start();// TIMED_WAITING線程
        new Thread(new Waiting(), "WaitingThread").start();// WAITING線程
        new Thread(new Blocked(), "BlockedThread-1").start();// 獲取鎖之後TIMED_WAITING
        new Thread(new Blocked(), "BlockedThread-2").start();// 獲取不到鎖,被阻塞BLOCKED
    }

    // 該線程一直運行
    static class RunThread implements Runnable {
        @Override
        public void run() {
            while (true) {
                System.out.println(1);
            }
        }
    }

    // 該線程不斷地進行睡眠 TIMED_WAITING
    static class TimeWaiting implements Runnable {
        @Override
        public void run() {
            while (true) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    // 該線程在Waiting.class實例上等待 WAITING
    static class Waiting implements Runnable {
        @Override
        public void run() {
            while (true) {
                synchronized (Waiting.class) {
                    try {
                        Waiting.class.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    // 該線程在Blocked.class實例上加鎖後,不會釋放該鎖
    static class Blocked implements Runnable {
        public void run() {
            synchronized (Blocked.class) {
                while (true) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}
  1. Export 導出 jar 包 ThreadState.jar

  2. Linux 環境或者 windows 的命令界面,運行 ThreadState.jar。

  3. 執行命令

    java -cp ThreadState.jar test.ThreadState

  4. 到 java 安裝目錄(我的目錄是:/usr/java/jdk1.8.0_161/bin)下,執行 ./jps ,顯示如下:

    5435 ThreadState

5435 就是 ThreadState.jar 程序的進程 id

  1. 執行命令 ./jstack 5435 ,查看線程狀態

// 名爲"BlockedThread-2" 的線程狀態:TIMED_WAITING
"BlockedThread-2" #13 prio=5 os_prio=0 tid=0x00007fa8f0157800 nid=0x154f waiting on condition [0x00007fa8c962e000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
        at java.lang.Thread.sleep(Native Method)
        at test.ThreadState$Blocked.run(ThreadState.java:61)
        - locked <0x00000000d9408370> (a java.lang.Class for test.ThreadState$Blocked)
        at java.lang.Thread.run(Thread.java:748)

// 名爲"BlockedThread-1" 的線程狀態:BLOCKED
"BlockedThread-1" #12 prio=5 os_prio=0 tid=0x00007fa8f0156000 nid=0x154e waiting for monitor entry [0x00007fa8c972f000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at test.ThreadState$Blocked.run(ThreadState.java:61)
        - waiting to lock <0x00000000d9408370> (a java.lang.Class for test.ThreadState$Blocked)
        at java.lang.Thread.run(Thread.java:748)

// 名爲"WaitingThread" 的線程狀態:WAITING
"WaitingThread" #11 prio=5 os_prio=0 tid=0x00007fa8f0154000 nid=0x154d in Object.wait() [0x00007fa8c9830000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x00000000d9408560> (a java.lang.Class for test.ThreadState$Waiting)
        at java.lang.Object.wait(Object.java:502)
        at test.ThreadState$Waiting.run(ThreadState.java:46)
        - locked <0x00000000d9408560> (a java.lang.Class for test.ThreadState$Waiting)
        at java.lang.Thread.run(Thread.java:748)

// 名爲"TimeWaitingThread" 的線程狀態:TIMED_WAITING
"TimeWaitingThread" #10 prio=5 os_prio=0 tid=0x00007fa8f0152800 nid=0x154c waiting on condition [0x00007fa8c9931000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
        at java.lang.Thread.sleep(Native Method)
        at test.ThreadState$TimeWaiting.run(ThreadState.java:31)
        at java.lang.Thread.run(Thread.java:748)

// 名爲"RunThread" 的線程狀態:RUNNABLE
"RunThread" #9 prio=5 os_prio=0 tid=0x00007fa8f0150800 nid=0x154b runnable [0x00007fa8c9a32000]
   java.lang.Thread.State: RUNNABLE
        at java.io.FileOutputStream.writeBytes(Native Method)
        at java.io.FileOutputStream.write(FileOutputStream.java:326)
        at java.io.BufferedOutputStream.flushBuffer(BufferedOutputStream.java:82)
        at java.io.BufferedOutputStream.flush(BufferedOutputStream.java:140)
        - locked <0x00000000d942e020> (a java.io.BufferedOutputStream)
        at java.io.PrintStream.write(PrintStream.java:482)
        - locked <0x00000000d9404b30> (a java.io.PrintStream)
        at sun.nio.cs.StreamEncoder.writeBytes(StreamEncoder.java:221)
        at sun.nio.cs.StreamEncoder.implFlushBuffer(StreamEncoder.java:291)
        at sun.nio.cs.StreamEncoder.flushBuffer(StreamEncoder.java:104)
        - locked <0x00000000d9404ae8> (a java.io.OutputStreamWriter)
        at java.io.OutputStreamWriter.flushBuffer(OutputStreamWriter.java:185)
        at java.io.PrintStream.write(PrintStream.java:527)
        - eliminated <0x00000000d9404b30> (a java.io.PrintStream)
        at java.io.PrintStream.print(PrintStream.java:597)
        at java.io.PrintStream.println(PrintStream.java:736)
        - locked <0x00000000d9404b30> (a java.io.PrintStream)
        at test.ThreadState$RunThread.run(ThreadState.java:20)
        at java.lang.Thread.run(Thread.java:748)

總結

Java 中創建線程兩種方法:繼承 Thread 和實現 Runable。線程的啓動調用 start 方法,線程執行 run 方法。

新構造的線程對象是由其 parent 線程來進行空間分配的和線程 id 的,新線程繼承 parent 線程的 group、是否爲 Daemon、優先級 priority 等屬性。

線程的狀態:NEW、RUNNABLE、RUNNING、BLOCKED、WAITING、TIMED_WAITING、DEAD。

Java 服務器問題排查過程中,常用的 jstack 命令查看線程狀態。

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