1. 程式人生 > >Java讀原始碼之Thread

Java讀原始碼之Thread

前言

JDK版本:1.8

閱讀了Object的原始碼,wait和notify方法與執行緒聯絡緊密,而且多執行緒已經是必備知識,那保持習慣,就從多執行緒的源頭Thread類開始讀起吧。由於該類比較長,只讀重要部分

原始碼

類宣告和重要屬性

package java.lang;

public class Thread implements Runnable {

    private volatile String name;
    // 優先順序
    private int            priority;
    //是否後臺
    private boolean     daemon = false;
    /* JVM state */
    private boolean     stillborn = false;
    // 要跑的任務
    private Runnable target;
    // 執行緒組
    private ThreadGroup group;
    // 上下文載入器
    private ClassLoader contextClassLoader;
    // 許可權控制上下文
    private AccessControlContext inheritedAccessControlContext;
    // 執行緒預設名字“Thread-{{ threadInitNumber }}”
    private static int threadInitNumber;
    // 區域性變數,每個執行緒擁有各自獨立的副本
    ThreadLocal.ThreadLocalMap threadLocals = null;
    // 有時候區域性變數需要被子執行緒繼承
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
    // 執行緒初始化時申請的JVM棧大小
    private long stackSize;
    // 執行緒ID
    private long tid;
    // 執行緒init之後的ID
    private static long threadSeqNumber;
    // 0就是執行緒還處於NEW狀態,沒start
    private volatile int threadStatus = 0;
    // 給LockSupport.park用的需要競爭的物件
    volatile Object parkBlocker;
    // 給中斷用的需要競爭的物件
    private volatile Interruptible blocker;
    // 執行緒最小優先順序
    public final static int MIN_PRIORITY = 1;
    // 執行緒預設優先順序
    public final static int NORM_PRIORITY = 5;
    // 執行緒最大優先順序
    public final static int MAX_PRIORITY = 10;

Java執行緒有幾種狀態?

// Thread類中的列舉
public enum State {
    // 執行緒剛創建出來還沒start
    NEW,
    // 執行緒在JVM中運行了,需要去競爭資源,例如CPU
    RUNNABLE,
    // 執行緒等待獲取物件監視器鎖,損被別人拿著就阻塞
    BLOCKED,
    // 執行緒進入等待池了,等待覺醒
    WAITING,
    // 指定了超時時間
    TIMED_WAITING,
    // 執行緒終止
    TERMINATED;
}

下面這個圖可以幫助理解Java執行緒的生命週期,這個圖要會畫!面試中被問到,當時畫的很不專業,難受!

建立

那麼執行緒如何進入初始New狀態呢?讓我們來看看構造,頭皮發麻,怎麼有七八個構造,這裡只貼了一個

public Thread() {
    init(null, null, "Thread-" + nextThreadNum(), 0);
}

還好都是呼叫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) {
        // 通過security獲取執行緒組,其實就是拿的當前執行緒的組
        if (security != null) {
            g = security.getThreadGroup();
        }

        // 獲取當前執行緒的組,這下確保肯定有執行緒組了
        if (g == null) {
            g = parent.getThreadGroup();
        }
    }

    // check一下組是否存在和是否有執行緒組修改許可權
    g.checkAccess();

    // 子類執行許可權檢查,子類不能重寫一些不是final的敏感方法
    if (security != null) {
        if (isCCLOverridden(getClass())) {
            security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
        }
    }
    // 組裡未啟動的執行緒數加1,長時間不啟動就會被回收
    g.addUnstarted();
    // 執行緒的組,是否後臺,優先順序,初始全和當前執行緒一樣
    this.group = g;
    this.daemon = parent.isDaemon();
    this.priority = parent.getPriority();
    if (security == null || isCCLOverridden(parent.getClass()))
        // 子類重寫check沒過或者就沒有security,這裡要check下是不是連裝載的許可權都沒有
        this.contextClassLoader = parent.getContextClassLoader();
    else
        this.contextClassLoader = parent.contextClassLoader;
    // 訪問控制上下文初始化
    this.inheritedAccessControlContext =
        acc != null ? acc : AccessController.getContext();
    // 任務初始化
    this.target = target;
    // 設定許可權
    setPriority(priority);
    // 如果有需要繼承的ThreadLocal區域性變數就copy一下
    if (inheritThreadLocals && parent.inheritableThreadLocals != null)
        this.inheritableThreadLocals =
        ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
    // 初始化JVM中待建立執行緒的棧大小
    this.stackSize = stackSize;

    // threadSeqNumber執行緒號加1
    tid = nextThreadID();
}

執行

現線上程已經是NEW狀態了,我們還需要呼叫start方法,讓執行緒進入RUNNABLE狀態,真正在JVM中快樂的跑起來,當獲得了執行任務所需要的資源後,JVM便會呼叫target(Runnable)的run方法。

注意:我們永遠不要對同一個執行緒物件執行兩次start方法

public synchronized void start() {
    // 0就是NEW狀態
    if (threadStatus != 0)
        throw new IllegalThreadStateException();

    // 把當前執行緒加到執行緒組的執行緒陣列中,然後nthreads執行緒數加1,nUnstartedThreads沒起的執行緒數減1
    group.add(this);

    boolean started = false;
    // 請求資源
    try {
        start0();
        started = true;
    } finally {
        try {
            if (!started) {
    // 起失敗啦,把當前執行緒從執行緒組的執行緒陣列中刪除,然後nthreads減1,nUnstartedThreads加1
                group.threadStartFailed(this);
            }
        } catch (Throwable ignore) {
            // start0出問題會自己列印堆疊資訊
        }
    }
}

private native void start0();

終止

現在我們的執行緒已經到RUNNABLE狀態了,一切順利的話任務執行完成,自動進入TERMINATED狀態,天有不測風雲,我們還會再各個狀態因為異常到達TERMINATED狀態。

Thread類為我們提供了interrupt方法,可以設定中斷標誌位,設定了中斷之後不一定有影響,還需要滿足一定的條件才能發揮作用:

  • RUNNABLE狀態下
    • 預設什麼都不會發生,需要程式碼中迴圈檢查 中斷標誌位
  • WAITING/TIMED_WAITING狀態下
    • 這兩個狀態下,會從物件等待池中出來,等拿到監視器鎖會丟擲InterruptedException異常,然後中斷標誌位被清空。
  • BLOCKED狀態下
    • 如果執行緒在等待鎖,對執行緒物件呼叫interrupt()只是會設定執行緒的中斷標誌位,執行緒依然會處於BLOCKED狀態
  • NEW/TERMINATE狀態下
    • 啥也不發生
// 設定別的執行緒中斷
public void interrupt() {
    if (this != Thread.currentThread())
        checkAccess();
    // 拿一個可中斷物件Interruptible的鎖
    synchronized (blockerLock) {
        Interruptible b = blocker;
        if (b != null) {
            interrupt0();           // 設定中斷標誌位
            b.interrupt(this);
            return;
        }
    }
    interrupt0();
}

// 獲取當前執行緒中斷標誌位,然後重置中斷標誌位
public static boolean interrupted() {
    return currentThread().isInterrupted(true);
}

// 檢查執行緒中斷標誌位
public boolean isInterrupted() {
    return isInterrupted(false);
}

等待

主線已經做完了,下面來看下支線任務,同樣重要哦。從執行緒狀態圖看到,RUNNABLE狀態可以變成BLOCKED,WAITING或TIMED_WAITING。

其中BLOCKED主要是同步方法競爭鎖等同步資源造成的,而TIMED_WAITING主要是加了超時時間,其他和WAITING的內容差不多,唯一多了一個sleep方法。

sleep

果不其然,sleep方法和Object.wait方法如出一轍,都是呼叫本地方法,提供毫秒和納秒兩種級別的控制,唯一區別就是,sleep不會放棄任何佔用的監視器鎖

public static native void sleep(long millis) throws InterruptedException;

// 納秒級別控制
public static void sleep(long millis, int nanos) throws InterruptedException {
    if (millis < 0) {
        throw new IllegalArgumentException("timeout value is negative");
    }

    if (nanos < 0 || nanos > 999999) {
        throw new IllegalArgumentException(
            "nanosecond timeout value out of range");
    }

    if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
        millis++;
    }

    sleep(millis);
}

join

join方法會讓執行緒進入WAITING,等待另一個執行緒的終止,整個方法和Object.wait方法也是很像,而且實現中也用到了wait

public final synchronized void join(long millis) throws InterruptedException {
    long base = System.currentTimeMillis();
    long now = 0;

    if (millis < 0) {
        throw new IllegalArgumentException("timeout value is negative");
    }

    if (millis == 0) {
        // 判斷呼叫join的執行緒是否活著,這裡的活著是指RUNNABLE,BLOCKED,WAITING,TIMED_WAITING這四種狀態,如果活著就一直等著,wait(0)意味著無限等
        while (isAlive()) {
            wait(0);
        }
    } else {
        while (isAlive()) {
            long delay = millis - now;
            if (delay <= 0) {
                break;
            }
            wait(delay);
            now = System.currentTimeMillis() - base;
        }
    }
}

// 納秒級別控制
public final synchronized void join(long millis, int nanos)
    throws InterruptedException {

    if (millis < 0) {
        throw new IllegalArgumentException("timeout value is negative");
    }

    if (nanos < 0 || nanos > 999999) {
        throw new IllegalArgumentException(
            "nanosecond timeout value out of range");
    }

    if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
        millis++;
    }

    join(millis);
}

public final void join() throws InterruptedException {
    join(0);
}

其他方法

yield

告訴作業系統的排程器:我的cpu可以先讓給其他執行緒,但是我佔有的同步資源不讓。

注意,排程器可以不理會這個資訊。這個方法幾乎沒用,除錯併發bug可能能派上用場

public static native void yield();

setPriority

有些場景是需要根據執行緒的優先順序來排程的,優先順序越大越先執行,最大10,預設5,最小1

public final void setPriority(int newPriority) {
    ThreadGroup g;
    checkAccess();
    if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
        throw new IllegalArgumentException();
    }
    if((g = getThreadGroup()) != null) {
        // 如果設定的優先順序,比執行緒所屬執行緒組中優先順序的最大值還大,我們需要更新最大值
        if (newPriority > g.getMaxPriority()) {
            newPriority = g.getMaxPriority();
        }
        // 本地方法
        setPriority0(priority = newPriority);
    }
}

棄用方法

有些熟悉的方法已經被棄用了,我們要避免使用

@Deprecated
public final void stop()
@Deprecated
public final synchronized void stop(Throwable obj)
@Deprecated
public void destroy()
@Deprecated
public final void suspend()
@Deprecated
public final void resume()
@Deprecated
public native int countStackFrames()

實踐

interrupt()

public class ThreadInterruptTest {

    /**
     * 如果我們同時呼叫了notify和interrupt方法,程式有可能正常執行結束,有可能丟擲異常結束,
     * 原因是不管是因為notify還是interrupt,執行緒離開了等待池,都需要去競爭鎖,
     * 如果interrupt呼叫瞬間拿到鎖,notify還沒有呼叫,就拋中斷異常
     * 如果是interrupt呼叫瞬間拿不到鎖,此時中斷標誌位被重置,然後notify把執行緒拉到正常軌道,就繼續執行不拋中斷異常
     */
    private static void testInterrupt() {
        Object object = new Object();
        Thread thread1 = new Thread(() -> {
            synchronized (object) {
                try {
                    object.wait();
                    System.out.println("我還活著!");
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    e.printStackTrace();
                }

            }

        });

        thread1.start();

        new Thread(() -> {
            // 只為了演示,實際很少用到這些方法,而且我們在執行中斷的同步程式碼塊中最好不要做別的事情,例如這裡的notify
            synchronized (object) {
                thread1.interrupt();
                object.notify();
            }
        }).start();
    }

    public static void main(String[] args) {
        for (int i = 0; i <5 ; i++) {
            ThreadInterruptTest.testInterrupt();
        }

    }
}
/**
 * 輸出:
 * 我還活著!
 * java.lang.InterruptedException
 *  at java.lang.Object.wait(Native Method)
 *  at java.lang.Object.wait(Object.java:502)
 *  at study.ThreadInterruptTest.lambda$testInterrupt$0(ThreadInterruptTest.java:15)
 *  at java.lang.Thread.run(Thread.java:748)
 * java.lang.InterruptedException
 *  at java.lang.Object.wait(Native Method)
 *  at java.lang.Object.wait(Object.java:502)
 *  at study.ThreadInterruptTest.lambda$testInterrupt$0(ThreadInterruptTest.java:15)
 *  at java.lang.Thread.run(Thread.java:748)
 * 我還活著!
 * java.lang.InterruptedException
 *  at java.lang.Object.wait(Native Method)
 *  at java.lang.Object.wait(Object.java:502)
 *  at study.ThreadInterruptTest.lambda$testInterrupt$0(ThreadInterruptTest.java:15)
 *  at java.lang.Thread.run(Thread.java:748)
 *
 */

join()

public class ThreadJoinTest {
    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            System.out.println("你好");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("你更好!");
        });

        thread1.start();

        new Thread(() -> {
            System.out.println("你也好");
            try {
                thread1.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("你最好!!");
        }).start();
    }

    /**
     * 輸出:
     * 你好
     * 你也好
     * 你更好!
     * 你最好!!
     */
}