在 Java 中,使用 Thread 類能夠在操做系統層面建立線程,並綁定到對應的 Thread 類實例中。利用線程異步地執行任務,是併發編程的基礎。本文經過閱讀 Thread 源碼,瞭解線程狀態的定義,線程調度的相關方法,以及對線程中斷的處理等。java
本文基於 jdk1.8.0_91
線程類 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
「建立線程」是一個模糊的概念,某些場景下指的是在內存中建立 Thread 類的實例,在另外一些場景下指的是在操做系統層面建立原生線程。爲了區分這兩種大相徑庭的狀況,本文將建立 Thread 類的實例稱爲 「建立線程」,將建立操做系統原生線程稱爲 「啓動線程」。安全
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
在 JDK 官方文檔中,介紹了 Thread 類實例化的兩種方式:less
There are two ways to create a new thread of execution.
One is to declare a class to be a subclass ofThread
. This subclass should override therun
method of classThread
. 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 theRunnable
interface. That class then implements therun
method. An instance of the class can then be allocated, passed as an argument when creatingThread
, and started.
編寫 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 接口的 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();
執行 new java.lang.Thread().start()
會在操做系統上建立並啓動一個原生線程。
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();
根據《深刻理解 Java 虛擬機》第十二章第四節,實現線程主要有三種方式:
HotSpot 虛擬機採用 1:1 的線程模型,它的每個 Java 線程都是直接映射到一個操做系統原生線程來實現的,並且中間沒有額外的間接結構,因此 HotSpot 本身是不會去幹涉線程調度的(能夠設置線程優先級給操做系統提供調度建議),全權交給底下的操做系統去處理,因此什麼時候凍結或喚醒線程、該給線程分配多少處理器執行時間、該把線程安排給哪一個處理器核心去執行等,都是由操做系統完成的,也都是由操做系統全權決定的。
在 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; }
根據定義,線程具備 6 種狀態:
線程在同一時間下,只會處於一種狀態。這些狀態是在 JVM 層面的,不會映射到操做系統層面的線程狀態。
還沒有啓動的線程處於該狀態。
在 JVM 中運行的線程處於該狀態。
該狀態下的線程正在 JVM 中運行,可是可能須要等待操做系統的資源,如 CPU 調度等。
等待監視器鎖(monitor lock)的線程處於這個狀態。
分爲兩種狀況:
無限期等待中的線程處於該狀態。
調用如下方法之一,線程會處於該狀態。
處於等待狀態的線程,在等待的是其餘線程執行特定的操做。
有限期等待中的線程處於該狀態。
調用如下方法之一(入參均須要傳入超時時間),線程會處於該狀態。
已退出的線程處於該狀態。
說明線程已經結束運行。
Java 線程 6 種狀態看起來挺複雜的,但其實 BLOCKED,WATTING,TIMED_WAITING 都會使線程處於阻塞狀態,因此咱們將這三類都歸類爲阻塞狀態。所以,Java 線程生命週期就能夠簡化爲下圖:
在 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 }
當前線程調用 Thread#sleep、Object#wait、Thread#join 都會進入等待狀態(WAITING/TIMED_WAITING)。
/** * 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)
/** * 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)
/** * 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 執行完成。
分析一下整個過程:
threadB.join()
,因爲 Thread#join 方法是非靜態的、由 synchronized 修飾的,threadA 首先須要獲取 threadB 這個對象的鎖。this.isAlive()
是 threadB 對象的方法,所以是 threadA 來檢查 threadB 是否存活。threadB.wait()
會釋放鎖,進入無限期等待狀態 WAITING。threadB.notifyAll()
方法喚醒在 threadB.wait()
上等待的線程。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 毫秒,指的是沒有超時時間。
線程調用 Thread#yield 並不會進入等待狀態。
/** * 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();
中斷(Interrupt)一個線程意味着在該線程完成任務以前中止其正在進行的一切,有效地停止其當前的操做。可是,沒有任何語言方面的需求一個被中斷的線程應該終止。中斷一個線程只是爲了引發該線程的注意,被中斷線程能夠決定如何應對中斷。
此外,雖然 Thread.stop 確實中止了一個正在運行的線程,可是這種方式是不安全的,可能會產生不可預料的結果,在 JDK 中已標記爲過期方法。
在 Java 中,每個線程都有一箇中斷標誌位,該標誌位用於表示線程的中斷狀態:已中斷、未中斷。
在 java.lang.Thread 中提供了檢查、清除和設置中斷標識的方法。
/** * 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); }
也就是說,threadA 執行 threadB.interrupt()
,則 threadB 會出現如下兩種結果之一(取決於 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();
已知線程調用 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(); } }
若是在循環中調用 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()); } }
new Thread()
會在內存中建立 Thread 類實例,而 new Thread().start()
纔會建立並啓動操做系統原生線程。