1. 線程的狀態
線程的狀態在 JDK 1.5 之後以枚舉的方式被定義在 Thread 的源碼中,它總共包含以下 6 個狀態:
- NEW,新建狀態,線程被創建出來,但尚未啓動時的線程狀態;
- RUNNABLE,就緒狀態,表示可以運行的線程狀態,它可能正在運行,或者是在排隊等待操作系統給它分配 CPU 資源;
- BLOCKED,阻塞等待鎖的線程狀態,表示處於阻塞狀態的線程正在等待監視器鎖,比如等待執行 synchronized 代碼塊或者使用 synchronized 標記的方法;
- WAITING,等待狀態,一個處於等待狀態的線程正在等待另一個線程執行某個特定的動作,比如,一個線程調用了 Object.wait() 方法,那它就在等待另一個線程調用 Object.notify() 或 Object.notifyAll() 方法;
- TIMED_WAITING,計時等待狀態,和等待狀態(WAITING)類似,它只是多了超時時間,比如調用了有超時時間設置的方法 Object.wait(long timeout) 和 Thread.join(long timeout) 等這些方法時,它纔會進入此狀態;
- TERMINATED,終止狀態,表示線程已經執行完成。
源碼如下:
public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
線程的工作模式是,首先先要創建線程並指定線程需要執行的業務方法,然後再調用線程的 start() 方法,此時線程就從 NEW(新建)狀態變成了 RUNNABLE(就緒)狀態,此時線程會判斷要執行的方法中有沒有 synchronized 同步代碼塊,如果有並且其他線程也在使用此鎖,那麼線程就會變爲 BLOCKED(阻塞等待)狀態,當其他線程使用完此鎖之後,線程會繼續執行剩餘的方法。
當遇到 Object.wait() 或 Thread.join() 方法時,線程會變爲 WAITING(等待狀態)狀態,如果是帶了超時時間的等待方法,那麼線程會進入 TIMED_WAITING(計時等待)狀態,當有其他線程執行了 notify() 或 notifyAll() 方法之後,線程被喚醒繼續執行剩餘的業務方法,直到方法執行完成爲止,此時整個線程的流程就執行完了,執行流程如下圖所示:
2. 常見面試題
2.1 BLOCKED 和 WAITING 的區別
首先它們狀態形成的調用方法不同,其次 BLOCKED 可以理解爲當前線程還處於活躍狀態,只是在阻塞等待其他線程使用完某個鎖資源;而 WAITING 則是因爲自身調用了 Object.wait() 或着是 Thread.join() 又或者是 LockSupport.park() 而進入等待狀態,只能等待其他線程執行某個特定的動作才能被繼續喚醒,比如當線程因爲調用了 Object.wait() 而進入 WAITING 狀態之後,則需要等待另一個線程執行 Object.notify() 或 Object.notifyAll() 才能被喚醒。
2.2 start() 和 run() 的區別
從源碼上看,start() 方法屬於Thread 自身的方法,並且使用了synchronized 來保證線程安全,源碼如下:
public synchronized void start() {
// 狀態驗證,不等於 NEW 的狀態會拋出異常
if (threadStatus != 0)
throw new IllegalThreadStateException();
group.add(this); // 通知線程組,此線程即將啓動
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
// 不處理任何異常,如果 start0 拋出異常,則它將被傳遞到調用堆棧上
}
}
}
run() 方法爲 Runnable 的抽象方法,必須由調用類重寫此方法,重寫的 run() 方法其實就是此線程要執行的業務方法,源碼如下 :
public class Thread implements Runnable {
// 忽略其他方法......
private Runnable target;
@Override
public void run() {
if (target != null) {
target.run();
}
}
}
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
從執行的效果來說,start() 方法可以開啓多線程,讓線程從 NEW 狀態轉換成 RUNNABLE 狀態,而 run() 方法只是一個普通的方法。其次,它們可調用的次數不同,start() 方法不能被多次調用,否則會拋出 java.lang.IllegalStateException;而 run() 方法可以進行多次調用,因爲它只是一個普通的方法而已。
2.3 線程優先級
在Thread 源碼中和線程優先級相關的屬性有 3 個,線程的優先級可以理解爲線程搶佔CPU時間片的概率,優先級越高的線程優先執行的概率就越大,但並不能保證優先級高的線程一定先執行。
// 線程可以擁有的最小優先級
public final static int MIN_PRIORITY = 1;
// 線程默認優先級
public final static int NORM_PRIORITY = 5;
// 線程可以擁有的最大優先級
public final static int MAX_PRIORITY = 10
在程序中我們可以通過 Thread.setPriority() 來設置優先級,setPriority() 源碼如下:
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);
}
}
2.4 線程的常用方法
(1)join() 方法
在一個線程中調用 other.join() ,這時候當前線程會讓出執行權給 other 線程,直到 other 線程執行完或者過了超時時間之後再繼續執行當前線程,join() 源碼如下:從源碼中可以看出 join() 方法底層是通過 wait() 方法來實現的。
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
// 超時時間不能小於 0
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
// 等於 0 表示無限等待,直到線程執行完爲之
if (millis == 0) {
// 判斷子線程 (其他線程) 爲活躍線程,則一直等待
while (isAlive()) {
wait(0);
}
} else {
// 循環判斷
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
(2)yield() 方法
從源碼可以看出,yield() 方法爲本地方法,源碼如下:yield() 方法表示給線程調度器一個當前線程願意出讓 CPU 使用權的暗示,但是線程調度器可能會忽略這個暗示。但是yield() 方法非常不穩定,線程調度器不一定會採納 yield() 出讓 CPU 使用權的建議。
public static native void yield();