閱讀 JDK 源碼:線程類 Thread

在 Java 中,使用 Thread 類能夠在操做系統層面建立線程,並綁定到對應的 Thread 類實例中。利用線程異步地執行任務,是併發編程的基礎。本文經過閱讀 Thread 源碼,瞭解線程狀態的定義,線程調度的相關方法,以及對線程中斷的處理等。java

本文基於 jdk1.8.0_91

1. 繼承體系

線程類 Thread 實現了 Runnable 接口,而且具備一個 Runnable 屬性,表示須要線程執行的任務。
Thread#run 方法內部調用了屬性 Runnable 的 run 方法;若 Runnable 屬性爲空則什麼也不作。編程

/**
 * A <i>thread</i> is a thread of execution in a program. The Java
 * Virtual Machine allows an application to have multiple threads of
 * execution running concurrently.
 *
 * @author  unascribed
 * @see     Runnable
 * @see     Runtime#exit(int)
 * @see     #run()
 * @see     #stop()
 * @since   JDK1.0
 */
public class Thread implements Runnable {

    /* What will be run. */
    private Runnable target;
    
    /**
     * If this thread was constructed using a separate
     * <code>Runnable</code> run object, then that
     * <code>Runnable</code> object's <code>run</code> method is called;
     * otherwise, this method does nothing and returns.
     * <p>
     * Subclasses of <code>Thread</code> should override this method.
     *
     * @see     #start()
     * @see     #stop()
     * @see     #Thread(ThreadGroup, Runnable, String)
     */
    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }
}

若是直接執行 Thread#run 只是一個普通的調用,並不會啓動新的線程來執行任務。
正確是用法是執行 Thread#start 來啓動新的線程,由該線程來調用 Thread#run 執行任務。segmentfault

2. 線程的建立

「建立線程」是一個模糊的概念,某些場景下指的是在內存中建立 Thread 類的實例,在另外一些場景下指的是在操做系統層面建立原生線程。爲了區分這兩種大相徑庭的狀況,本文將建立 Thread 類的實例稱爲 「建立線程」,將建立操做系統原生線程稱爲 「啓動線程」。安全

2.1 構造函數

public Thread() {
    init(null, null, "Thread-" + nextThreadNum(), 0);
}
public Thread(String name) {
    init(null, null, name, 0);
}
public Thread(Runnable target) {
    init(null, target, "Thread-" + nextThreadNum(), 0);
}
public Thread(Runnable target, String name) {
    init(null, target, name, 0);
}
public Thread(ThreadGroup group, Runnable target) {
    init(group, target, "Thread-" + nextThreadNum(), 0);
}
public Thread(ThreadGroup group, String name) {
    init(group, null, name, 0);
}
public Thread(ThreadGroup group, Runnable target, String name) {
    init(group, target, name, 0);
}
public Thread(ThreadGroup group, Runnable target, String name, long stackSize) {
    init(group, target, name, stackSize);
}

最終都是調用 Thread#init 方法:併發

/**
 * Initializes a Thread with the current AccessControlContext.
 * @see #init(ThreadGroup,Runnable,String,long,AccessControlContext)
 */
private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize) {
    init(g, target, name, stackSize, null);
}

/**
 * Initializes a Thread.
 *
 * @param g the Thread group
 * @param target the object whose run() method gets called
 * @param name the name of the new Thread
 * @param stackSize the desired stack size for the new thread, or
 *        zero to indicate that this parameter is to be ignored.
 * @param acc the AccessControlContext to inherit, or
 *            AccessController.getContext() if null
 */
private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc) {
    ...
}

入參說明:app

  • ThreadGroup g(線程組)
  • Runnable target (Runnable 對象,表示線程需執行的任務)
  • String name (線程的名字)
  • long stackSize (爲線程分配的棧的大小,若爲 0 則表示忽略這個參數)
  • AccessControlContext acc (繼承給線程的訪問權限控制上下文,默認爲空,表示使用 AccessController.getContext())

2.2 建立線程實例

在 JDK 官方文檔中,介紹了 Thread 類實例化的兩種方式:less

There are two ways to create a new thread of execution.
One is to declare a class to be a subclass of Thread. This subclass should override the run method of class Thread. An instance of the subclass can then be allocated and started.
The other way to create a thread is to declare a class that implements the Runnable interface. That class then implements the run method. An instance of the class can then be allocated, passed as an argument when creating Thread, and started.

方式一:繼承 Thread 類

編寫 Thread 的子類,重寫 Thread 類的 run 方法。建立該子類的實例以啓動線程執行任務。異步

例如,計算大於某一規定值的質數的線程能夠寫成:ide

class PrimeThread extends Thread {
    long minPrime;
    PrimeThread(long minPrime) {
        this.minPrime = minPrime;
    }

    public void run() {
        // compute primes larger than minPrime
        ...
    }
}

下列代碼會建立並啓動一個線程:函數

PrimeThread p = new PrimeThread(143);
p.start();

方式二:實現 Runnable 接口

編寫 Runnable 接口實現類,實現 Runnable 接口的 run 方法。在建立 Thread 時將該 Runnable 類的實例做爲一個參數來傳遞。

class PrimeRun implements Runnable {
    long minPrime;
    PrimeRun(long minPrime) {
        this.minPrime = minPrime;
    }

    public void run() {
        // compute primes larger than minPrime
        ...
    }
}

下列代碼會建立並啓動一個線程:

PrimeRun p = new PrimeRun(143);
new Thread(p).start();

3. 線程的啓動

3.1 啓動系統線程

執行 new java.lang.Thread().start() 會在操做系統上建立並啓動一個原生線程。

  • java.lang.Thread#start 方法,內部調用了 native 的 start0 方法。該方法會由 JVM 映射到系統層面建立並啓動線程,將該線程與 java.lang.Thread 對象進行綁定,隨後 JVM 會調用該 java.lang.Thread 對象的 run 方法。
  • 該操做的結果是會出現兩個併發執行的線程,一個是發起 Thread#start 調用的當前線程,另外一個是新建立的會執行 Thread#run 的線程。
  • 注意,屢次啓動一個線程是非法的。特別是當線程已經結束執行後,不能再從新啓動。

java.lang.Thread#start

/**
 * Causes this thread to begin execution; the Java Virtual Machine
 * calls the <code>run</code> method of this thread.
 * <p>
 * The result is that two threads are running concurrently: the
 * current thread (which returns from the call to the
 * <code>start</code> method) and the other thread (which executes its
 * <code>run</code> method).
 * <p>
 * It is never legal to start a thread more than once.
 * In particular, a thread may not be restarted once it has completed
 * execution.
 *
 * @exception  IllegalThreadStateException  if the thread was already
 *               started.
 * @see        #run()
 * @see        #stop()
 */
public synchronized void start() {
    /**
     * This method is not invoked for the main method thread or "system"
     * group threads created/set up by the VM. Any new functionality added
     * to this method in the future may have to also be added to the VM.
     *
     * A zero status value corresponds to state "NEW".
     */
    if (threadStatus != 0)
        throw new IllegalThreadStateException();

    /* Notify the group that this thread is about to be started
     * so that it can be added to the group's list of threads
     * and the group's unstarted count can be decremented. */
    group.add(this);

    boolean started = false;
    try {
        start0(); // 由 JVM 映射到系統層面,建立線程!
        started = true;
    } finally {
        try {
            if (!started) {
                group.threadStartFailed(this);
            }
        } catch (Throwable ignore) {
            /* do nothing. If start0 threw a Throwable then
              it will be passed up the call stack */
        }
    }
}

private native void start0();

3.2 JVM 線程模型

根據《深刻理解 Java 虛擬機》第十二章第四節,實現線程主要有三種方式:

  • 使用內核線程實現(1:1 實現);
  • 使用用戶線程實現(1:N 實現);
  • 使用用戶線程加輕量級進程混合實現(N:M 實現)。

HotSpot 虛擬機採用 1:1 的線程模型,它的每個 Java 線程都是直接映射到一個操做系統原生線程來實現的,並且中間沒有額外的間接結構,因此 HotSpot 本身是不會去幹涉線程調度的(能夠設置線程優先級給操做系統提供調度建議),全權交給底下的操做系統去處理,因此什麼時候凍結或喚醒線程、該給線程分配多少處理器執行時間、該把線程安排給哪一個處理器核心去執行等,都是由操做系統完成的,也都是由操做系統全權決定的。
1:1線程模型

  • 輕量級進程(Light Weight Process,LWP)是內核線程的一種高級接口,就是咱們一般意義上所講的線程。程序通常不會直接使用內核線程,而是使用輕量級進程。
  • 內核線程(Kernel-Level Thread,KLT)就是直接由操做系統內核(Kernel)支持的線程,這種線程由內核來完成線程切換。
  • 內核(Kernel)經過操縱調度器(Scheduler)對線程進行調度,並負責將線程的任務映射到各個處理器上。

4. 線程的狀態

4.1 狀態定義

在 java.lang.Thread 類中,定義了 threadStatus 屬性和 State 枚舉來描述線程的狀態。

/* Java thread status for tools,
 * initialized to indicate thread 'not yet started'
 */
private volatile int threadStatus = 0;

/**
 * A thread state.
 * <p>
 * A thread can be in only one state at a given point in time.
 * These states are virtual machine states which do not reflect
 * any operating system thread states.
 *
 * @since   1.5
 * @see #getState
 */
public enum State {
    /**
     * Thread state for a thread which has not yet started.
     */
    NEW,

    /**
     * Thread state for a runnable thread.  A thread in the runnable
     * state is executing in the Java virtual machine but it may
     * be waiting for other resources from the operating system
     * such as processor.
     */
    RUNNABLE,

    /**
     * Thread state for a thread blocked waiting for a monitor lock.
     * A thread in the blocked state is waiting for a monitor lock
     * to enter a synchronized block/method or
     * reenter a synchronized block/method after calling
     * {@link Object#wait() Object.wait}.
     */
    BLOCKED,

    /**
     * Thread state for a waiting thread.
     * A thread is in the waiting state due to calling one of the
     * following methods:
     * <ul>
     *   <li>{@link Object#wait() Object.wait} with no timeout</li>
     *   <li>{@link #join() Thread.join} with no timeout</li>
     *   <li>{@link LockSupport#park() LockSupport.park}</li>
     * </ul>
     *
     * <p>A thread in the waiting state is waiting for another thread to
     * perform a particular action.
     *
     * For example, a thread that has called <tt>Object.wait()</tt>
     * on an object is waiting for another thread to call
     * <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
     * that object. A thread that has called <tt>Thread.join()</tt>
     * is waiting for a specified thread to terminate.
     */
    WAITING,

    /**
     * Thread state for a waiting thread with a specified waiting time.
     * A thread is in the timed waiting state due to calling one of
     * the following methods with a specified positive waiting time:
     * <ul>
     *   <li>{@link #sleep Thread.sleep}</li>
     *   <li>{@link Object#wait(long) Object.wait} with timeout</li>
     *   <li>{@link #join(long) Thread.join} with timeout</li>
     *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
     *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
     * </ul>
     */
    TIMED_WAITING,

    /**
     * Thread state for a terminated thread.
     * The thread has completed execution.
     */
    TERMINATED;
}

4.2 狀態說明

根據定義,線程具備 6 種狀態:

  • NEW:A thread that has not yet started is in this state.
  • RUNNABLE:A thread executing in the Java virtual machine is in this state.
  • BLOCKED:A thread that is blocked waiting for a monitor lock is in this state.
  • WAITING:A thread that is waiting indefinitely for another thread to perform a particular action is in this state.
  • TIMED_WAITING:A thread that is waiting for another thread to perform an action for up to a specified waiting time is in this state.
  • TERMINATED:A thread that has exited is in this state.

線程在同一時間下,只會處於一種狀態。這些狀態是在 JVM 層面的,不會映射到操做系統層面的線程狀態。

NEW

還沒有啓動的線程處於該狀態。

RUNNABLE

在 JVM 中運行的線程處於該狀態。
該狀態下的線程正在 JVM 中運行,可是可能須要等待操做系統的資源,如 CPU 調度等。

BLOCKED

等待監視器鎖(monitor lock)的線程處於這個狀態。
分爲兩種狀況:

  1. 首次進入 synchronized 塊時等待獲取鎖;
  2. 在調用 Object#wait、Object.wait 方法以後,重入 synchronized 塊時等待獲取鎖。

WAITING

無限期等待中的線程處於該狀態。
調用如下方法之一,線程會處於該狀態。

  • Object.wait
  • Thread.join
  • LockSupport.park

處於等待狀態的線程,在等待的是其餘線程執行特定的操做。

  • 調用 Object.wait 的線程,等待其餘線程調用 Object.notify 或 Object.notifyAll
  • 調用 Thread.join 的線程,等待其餘線程終止
  • 調用 LockSupport.park 的線程,等待其餘線程調用 LockSupport.unpark

TIMED_WAITING

有限期等待中的線程處於該狀態。
調用如下方法之一(入參均須要傳入超時時間),線程會處於該狀態。

  • Thread.sleep
  • Object.wait(long)
  • Thread.join(long)
  • LockSupport.parkNanos
  • LockSupport.parkUntil

TERMINATED

已退出的線程處於該狀態。
說明線程已經結束運行。

4.3 狀態轉移

Thread State

4.4 狀態分類

Java 線程 6 種狀態看起來挺複雜的,但其實 BLOCKED,WATTING,TIMED_WAITING 都會使線程處於阻塞狀態,因此咱們將這三類都歸類爲阻塞狀態。所以,Java 線程生命週期就能夠簡化爲下圖:

Thread State

4.5 狀態方法

在 java.lang.Thread 類中,定義了 getState() 方法獲取線程的狀態,定義了 isAlive() 方法判斷當前線程是否活躍。

/**
 * Returns the state of this thread.
 * This method is designed for use in monitoring of the system state,
 * not for synchronization control.
 *
 * @return this thread's state.
 * @since 1.5
 */
public State getState() {
    // get current thread state
    return sun.misc.VM.toThreadState(threadStatus);
}

/**
 * Tests if this thread is alive. A thread is alive if it has
 * been started and has not yet died.
 *
 * @return  <code>true</code> if this thread is alive;
 *          <code>false</code> otherwise.
 */
public final native boolean isAlive();

利用 isAlive() 方法,能夠用於判斷當前 Thread 類實例是否綁定了系統原生線程。

@Test
public void state() throws InterruptedException {
    Thread thread = new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    });
    System.out.println("thread.isAlive() = " + thread.isAlive());  // false
    System.out.println("thread.getState() = " + thread.getState());// NEW

    thread.start();
    Thread.sleep(200);
    System.out.println("thread.isAlive() = " + thread.isAlive());  // true
    System.out.println("thread.getState() = " + thread.getState());// TIMED_WAITING

    thread.join();
    System.out.println("thread.isAlive() = " + thread.isAlive());  // false
    System.out.println("thread.getState() = " + thread.getState());// TERMINATED
}

5. 線程調度

當前線程調用 Thread#sleep、Object#wait、Thread#join 都會進入等待狀態(WAITING/TIMED_WAITING)。

5.1 Thread#sleep

  • Thread#sleep 是一個靜態的 native 方法。
  • 做用是使當前線程休眠一段時間,時間單位爲毫秒。而且休眠過程當中不會釋聽任何監視器鎖。
  • 調用了 Thread#sleep 以後,當前線程會進入 TIMED_WAITING 狀態。
  • 若是其餘線程中斷了當前線程,則當前線程從休眠中被喚醒,會清除中斷狀態,並拋出 InterruptedException。
/**
 * Causes the currently executing thread to sleep (temporarily cease
 * execution) for the specified number of milliseconds, subject to
 * the precision and accuracy of system timers and schedulers. The thread
 * does not lose ownership of any monitors.
 *
 * @param  millis
 *         the length of time to sleep in milliseconds
 *
 * @throws  IllegalArgumentException
 *          if the value of {@code millis} is negative
 *
 * @throws  InterruptedException
 *          if any thread has interrupted the current thread. The
 *          <i>interrupted status</i> of the current thread is
 *          cleared when this exception is thrown.
 */
public static native void sleep(long millis) throws InterruptedException;

Thread#sleep 還有另一個版本,容許同時傳入毫秒值和納秒值,可是多出來的 nanos 最多隻會讓線程多休眠 1 毫秒,不經常使用。Thread#join 和 Object#wait 中都具備相似的須要傳入毫秒值和納秒值的方法,後續再也不贅述。

/**
 * Causes the currently executing thread to sleep (temporarily cease
 * execution) for the specified number of milliseconds plus the specified
 * number of nanoseconds, subject to the precision and accuracy of system
 * timers and schedulers. The thread does not lose ownership of any
 * monitors.
 *
 * @param  millis
 *         the length of time to sleep in milliseconds       // 毫秒
 *
 * @param  nanos
 *         {@code 0-999999} additional nanoseconds to sleep  // 納秒
 *
 * @throws  IllegalArgumentException
 *          if the value of {@code millis} is negative, or the value of
 *          {@code nanos} is not in the range {@code 0-999999}
 *
 * @throws  InterruptedException
 *          if any thread has interrupted the current thread. The
 *          <i>interrupted status</i> of the current thread is
 *          cleared when this exception is thrown.
 */
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);
}

關於 Thread#sleep(0)

  • Thread.Sleep(0) 並不是是真的要線程掛起 0 毫秒,意義在於此次調用 Thread.Sleep(0) 的當前線程確實的被凍結了一下,讓其餘線程有機會優先執行。
  • Thread.Sleep(0) 是當前線程暫時放棄 CPU,也就是釋放一些未用的時間片給其餘線程或進程使用,就至關於一個讓位動做。

5.2 Object#wait

  • Object#wait 是一個非靜態的 native 方法,須要在對象實例上使用。
  • 做用是使當前線程等待其餘線程執行特定的操做,時間單位爲毫秒。
  • 調用 Object#wait 方法以前,線程必須持有監視器鎖(monitor lock)。
  • 線程在等待過程當中,會釋放鎖,進入 TIMED_WAITING 或 WAITING 狀態。
  • 線程從等待中被喚醒,從新等待獲取鎖,進入 BLOCKED 狀態。
/**
 * Causes the current thread to wait until another thread invokes the
 * {@link java.lang.Object#notify()} method or the
 * {@link java.lang.Object#notifyAll()} method for this object, or
 * some other thread interrupts the current thread, or a certain
 * amount of real time has elapsed.
 */
public final native void wait(long timeout) throws InterruptedException;

/**
 * Causes the current thread to wait until another thread invokes the 
 * {@link java.lang.Object#notify()} method or the
 * {@link java.lang.Object#notifyAll()} method for this object.
 * In other words, this method behaves exactly as if it simply
 * performs the call {@code wait(0)}.
 */ 
public final void wait() throws InterruptedException {
    wait(0);
}

用法示例:

synchronized (obj) {
    while (<condition does not hold>) {
        obj.wait();
    }
    ... // Perform action appropriate to condition
}

關於 Object#wait(0)

  • Object#wait() 內部其實是調用 Object#wait(timeout),超時時間爲 0 毫秒,指的是沒有超時時間。
  • Object#wait(0) 被喚醒的條件:其餘線程調用了 Object#notify 或 Object#notifyAll,或者當前線程發生了中斷,或者發生虛假喚醒(spurious wakeup)。
  • Object#wait(timeout) 被喚醒的條件:其餘線程調用了 Object#notify 或 Object#notifyAll,或者當前線程發生了中斷,或者發生虛假喚醒,或者等待超時。

5.3 Thread#join

  • Thread#join 是一個非靜態且非 native 方法,須要在線程實例上使用。
  • 做用是使當前線程等待指定線程執行完畢,時間單位爲毫秒。
/**
 * Waits for this thread to die.
 */
public final void join() throws InterruptedException {
    join(0);
}

/**
 * Waits at most {@code millis} milliseconds for this thread to
 * die. A timeout of {@code 0} means to wait forever.
 *
 * <p> This implementation uses a loop of {@code this.wait} calls
 * conditioned on {@code this.isAlive}. As a thread terminates the
 * {@code this.notifyAll} method is invoked. It is recommended that
 * applications not use {@code wait}, {@code notify}, or
 * {@code notifyAll} on {@code Thread} instances.
 *
 * @param  millis
 *         the time to wait in milliseconds
 *
 * @throws  IllegalArgumentException
 *          if the value of {@code millis} is negative
 *
 * @throws  InterruptedException
 *          if any thread has interrupted the current thread. The
 *          <i>interrupted status</i> of the current thread is
 *          cleared when this exception is thrown.
 */
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) {
        while (isAlive()) {
            wait(0);
        }
    } else {
        while (isAlive()) {
            long delay = millis - now;
            if (delay <= 0) {
                break;
            }
            wait(delay);
            now = System.currentTimeMillis() - base;
        }
    }
}

調用 Thread#join 涉及到兩個線程之間的交互。

好比線程 threadA 在運行過程當中,碰到 threadB.join() 這一行代碼,表示 threadA 須要進入等待,直到 threadB 執行完成。

分析一下整個過程:

  1. threadA 執行代碼 threadB.join(),因爲 Thread#join 方法是非靜態的、由 synchronized 修飾的,threadA 首先須要獲取 threadB 這個對象的鎖。
  2. 若 threadA 獲取不到鎖,會進入阻塞狀態 BLOCKED;若能夠獲取鎖,則進入方法內部。
  3. 時間參數校驗經過後,則會執行 Thread#isAlive 校驗線程是否存活。注意,這裏的 this.isAlive() 是 threadB 對象的方法,所以是 threadA 來檢查 threadB 是否存活。
  4. 若 threadB 還存活,則 threadA 執行 threadB.wait() 會釋放鎖,進入無限期等待狀態 WAITING。
  5. 當 threadB 結束運行,進入 TERMINATED 狀態,會調用 threadB.notifyAll() 方法喚醒在 threadB.wait() 上等待的線程。
  6. 當 threadA 從等待狀態 WAITING 被喚醒後,從新獲取 threadB 對象鎖,循環檢查 threadB 線程是否已存活,若不存活則結束等待。

JDK 源碼註釋中說明了,當線程終止時,會調用 Object#notifyAll 方法。

As a thread terminates the {@code this.notifyAll} method is invoked.

示例代碼:

@Test
public void join() throws InterruptedException {
    Thread threadB = new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                System.out.println(Thread.currentThread().getName() + " 開始運行...");
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName() + " 結束運行...");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }, "threadB");
    Thread threadA = new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                System.out.println(Thread.currentThread().getName() + " 開始運行...");
                threadB.start();
                threadB.join();
                System.out.println(Thread.currentThread().getName() + " 結束運行...");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }, "threadA");
    threadA.start();
    threadA.join();
}

執行結果:

threadA 開始運行...
threadB 開始運行...
threadB 結束運行...
threadA 結束運行...

關於 Thread#join(0)

Thread#join() 內部其實是調用 Thread#join(timeout),超時時間爲 0 毫秒,指的是沒有超時時間。

5.4 Thread#yield

線程調用 Thread#yield 並不會進入等待狀態。

  • Thread#yield 是一個靜態的 native 方法。
  • 做用是使當前線程讓出 CPU。但對於 CPU 只是一個建議,有可能一個線程剛讓出 CPU,而後又立馬得到了 CPU。
  • 與之相對,Thread#sleep 方法必定會讓出 CPU 資源,而且休眠指定的時間,不參與 CPU 的競爭。
  • 執行 Thread#yield 後線程仍處於 RUNNABLE 狀態,而執行 Thread#sleep 後線程會從 RUNNABLE 變爲 TIMED_WAITING 狀態。
/**
 * A hint to the scheduler that the current thread is willing to yield
 * its current use of a processor. The scheduler is free to ignore this
 * hint.
 *
 * <p> Yield is a heuristic attempt to improve relative progression
 * between threads that would otherwise over-utilise a CPU. Its use
 * should be combined with detailed profiling and benchmarking to
 * ensure that it actually has the desired effect.
 *
 * <p> It is rarely appropriate to use this method. It may be useful
 * for debugging or testing purposes, where it may help to reproduce
 * bugs due to race conditions. It may also be useful when designing
 * concurrency control constructs such as the ones in the
 * {@link java.util.concurrent.locks} package.
 */
public static native void yield();

6. 中斷

中斷(Interrupt)一個線程意味着在該線程完成任務以前中止其正在進行的一切,有效地停止其當前的操做。可是,沒有任何語言方面的需求一個被中斷的線程應該終止。中斷一個線程只是爲了引發該線程的注意,被中斷線程能夠決定如何應對中斷。

此外,雖然 Thread.stop 確實中止了一個正在運行的線程,可是這種方式是不安全的,可能會產生不可預料的結果,在 JDK 中已標記爲過期方法。

6.1 中斷狀態

在 Java 中,每個線程都有一箇中斷標誌位,該標誌位用於表示線程的中斷狀態:已中斷、未中斷。

在 java.lang.Thread 中提供了檢查、清除和設置中斷標識的方法。

6.2 檢查中斷

/**
 * Tests if some Thread has been interrupted.  The interrupted state
 * is reset or not based on the value of ClearInterrupted that is
 * passed.
 */
private native boolean isInterrupted(boolean ClearInterrupted);

java.lang.Thread#isInterrupted(boolean) 是一個 native 方法,用於檢查線程是否已中斷,入參 ClearInterrupted 用於控制是否清除中斷狀態。

調用該 native 方法的具備如下兩個:

Thread#isInterrupted 檢查指定線程的中斷狀態,不清除該線程的中斷狀態。

/**
 * Tests whether this thread has been interrupted.  The <i>interrupted
 * status</i> of the thread is unaffected by this method.
 *
 * <p>A thread interruption ignored because a thread was not alive
 * at the time of the interrupt will be reflected by this method
 * returning false.
 *
 * @return  <code>true</code> if this thread has been interrupted;
 *          <code>false</code> otherwise.
 * @see     #interrupted()
 * @revised 6.0
 */
public boolean isInterrupted() {
    return isInterrupted(false);
}

Thread#interrupted 檢查當前線程的中斷狀態,並清除當前線程的中斷狀態。

/**
 * Tests whether the current thread has been interrupted.  The 
 * <i>interrupted status</i> of the thread is cleared by this method.  In
 * other words, if this method were to be called twice in succession, the
 * second call would return false (unless the current thread were
 * interrupted again, after the first call had cleared its interrupted
 * status and before the second call had examined it).
 *
 * <p>A thread interruption ignored because a thread was not alive
 * at the time of the interrupt will be reflected by this method
 * returning false.
 *
 * @return  <code>true</code> if the current thread has been interrupted;
 *          <code>false</code> otherwise.
 * @see #isInterrupted()
 * @revised 6.0
 */
public static boolean interrupted() {
    return currentThread().isInterrupted(true);
}

6.2 發起中斷

  • 設置線程的中斷狀態爲已中斷。
  • 若是線程在調用 Object 類的 wait()、wait(long) 或 wait(long, int) 方法,或者調用 Thread 類的 join()、join(long)、join(long, int)、sleep(long) 或 sleep(long, int) 方法過程當中受阻,則其中斷狀態將被清除,它還將收到一個 InterruptedException。
  • 中斷一個已經終止的線程不會有任何影響。

也就是說,threadA 執行 threadB.interrupt(),則 threadB 會出現如下兩種結果之一(取決於 threadB 自身):

  • threadB 的中斷狀態爲未中斷,且拋出 InterruptedException;
  • threadB 的中斷狀態爲已中斷,不拋出異常。

java.lang.Thread#interrupt

/**
 * Interrupts this thread.
 *
 * @throws  SecurityException
 *          if the current thread cannot modify this thread
 *
 * @revised 6.0
 * @spec JSR-51
 */
public void interrupt() {
    if (this != Thread.currentThread())
        checkAccess();

    synchronized (blockerLock) {
        Interruptible b = blocker;
        if (b != null) {
            interrupt0();           // Just to set the interrupt flag
            b.interrupt(this);
            return;
        }
    }
    interrupt0();
}

private native void interrupt0();

6.3 如何正確處理中斷

已知線程調用 Object#wait、Thread#join、Thread#sleep 方法進入等待狀態時,會被其餘線程調用 Thread#interrupt 喚醒。
此時當前線程的中斷狀態會被清除,並拋出 InterruptedException。
若是當前線程處於某種緣由沒法傳遞 InterruptedException 異常,最好經過再次調用 interrupt 來恢復中斷的狀態,以供上層調用者處理。

錯誤的處理方式:

void func() {
    try {
        Thread.sleep(50);
    } catch (InterruptedException e) {
        // nothing
    }
}

正確的處理方式:

void func() throw InterruptedException {
    Thread.sleep(50);
}

或者

void func() {
    try {
        Thread.sleep(50);
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    }
}

6.4 不要在循環檢查中斷

若是在循環中調用 sleep,除非正確地處理中斷異常,不然不要去檢測中斷狀態。

錯誤的處理方式:

@Test
public void dealInterrupt() {
    Thread subThread = new Thread(new Runnable() {
        @Override
        public void run() {
            int i = 0;
            while (!Thread.currentThread().isInterrupted()) { // 循環檢查中斷狀態
                try {
                    System.out.println(Thread.currentThread().getName() + " 開始第【" + i + "】休眠...");
                    Thread.sleep(1000);
                    System.out.println(Thread.currentThread().getName() + " 結束第【" + i + "】休眠...");
                    ++i;
                } catch (InterruptedException e) { // 若是調用sleep受阻,會拋出異常,同時中斷狀態true將被清除爲false
                    System.out.println(Thread.currentThread().getName() + " " + e.getMessage());
                    // 只要正確地處理中斷,也可讓循環中止。
                    // Thread.currentThread().interrupt();
                }
            }
        }
    });
    subThread.start();

    // 主線程執行一段時間,中斷子線程,再繼續觀察子線程一段時間
    try {
        Thread.sleep(1000);
        subThread.interrupt();
        Thread.sleep(5000);
    } catch (InterruptedException e) {
        System.out.println(Thread.currentThread().getName() + " " + e.getMessage());
    }
}

該例子會出現兩種執行結果:

若是子線程在休眠當中被中斷,因爲會清除中斷狀態,致使子線程會無限循環下去。

Thread-0 開始第【0】休眠...
Thread-0 sleep interrupted
Thread-0 開始第【0】休眠...
Thread-0 結束第【0】休眠...
Thread-0 開始第【1】休眠...
Thread-0 結束第【1】休眠...
Thread-0 開始第【2】休眠...
Thread-0 結束第【2】休眠...
Thread-0 開始第【3】休眠...
Thread-0 結束第【3】休眠...
Thread-0 開始第【4】休眠...
Thread-0 結束第【4】休眠...
Thread-0 開始第【5】休眠...

若是子線程在休眠事後被中斷,因爲會設置中斷狀態,子線程能夠獲得終止。

Thread-0 開始第【0】休眠...
Thread-0 結束第【0】休眠...

正確的處理方式:

@Test
public void dealInterrupt02() {
    Thread subThread = new Thread(new Runnable() {
        @Override
        public void run() {
            int i = 0;
            boolean isLoop = true;
            while (isLoop) { // 使用自定的循環控制標識
                try {
                    System.out.println(Thread.currentThread().getName() + " 開始第【" + i + "】休眠...");
                    Thread.sleep(1000);
                    System.out.println(Thread.currentThread().getName() + " 結束第【" + i + "】休眠...");
                    ++i;
                } catch (InterruptedException e) {
                    System.out.println(Thread.currentThread().getName() + " " + e.getMessage());
                    isLoop = false;
                }
            }
        }
    });
    subThread.start();

    // 主線程執行一段時間,中斷子線程,再繼續觀察子線程一段時間
    try {
        Thread.sleep(1000);
        subThread.interrupt();
        Thread.sleep(5000);
    } catch (InterruptedException e) {
        System.out.println(Thread.currentThread().getName() + " " + e.getMessage());
    }
}

6.5 如何正確中止線程

  1. 任務中通常都會有循環結構,只要用一個標記控制住循環,就能夠結束任務。
  2. 若是線程處於等待狀態,沒法讀取標記,此時可使用 interrupt() 方法將線程從等待狀態強制恢復到運行狀態中來,讓線程具有 CPU 的執行資格,繼而自行決定是否應該終止。

7. 總結

  1. 建立線程具備兩種不一樣的含義,new Thread() 會在內存中建立 Thread 類實例,而 new Thread().start() 纔會建立並啓動操做系統原生線程。
  2. JVM 採用了 1:1 的線程模型,每個 Java 線程都是直接映射到一個操做系統原生線程。
  3. Thread 類中定義了 6 種線程狀態,不會映射到操做系統層面的線程狀態。
  4. Thread#sleep、Object#wait、Thread#join、Thread#yield 和 LockSupport 相關方法均可以用於調度線程。
  5. 線程中具備中斷狀態,應當使用中斷來終止一個線程的運行。當前線程被中斷時,須要正確地處理中斷。

做者:Sumkor
連接:https://segmentfault.com/a/11...

相關文章
相關標籤/搜索