10 張圖聊聊線程的生命周期和常用 APIs
今天我們來說一下線程的生命周期和常用 APIs:我們需要非常清楚的知道線程的各種狀態(tài),比如排查程序運行慢的原因時,就需要看下是不是哪里被阻塞了;另外它也是面試時非常喜歡問的,如果基礎內(nèi)容都答不好,恐怕直接就掛了。
本文分為兩大部分,
1.線程的 6 大狀態(tài);
2.多線程常用的 APIs:
- join()
- wait()
- notify()
- yield()
- sleep()
- currentThread()
- getName()
- getId()
- getPriority()
- setPriority()
- stop()
線程狀態(tài)
關于線程的狀態(tài),網(wǎng)上各種說法都有,比較流行的是 5 種或者 6 種。關于 5 種狀態(tài)的那個版本我沒有找到理論依據(jù),如果有小伙伴清楚的也歡迎留言指出。
我這里所寫的是根據(jù) java.lang.Thread 的源碼,線程有以下 6 大狀態(tài):
- public enum State {
- NEW,
- RUNNABLE,
- BLOCKED,
- WAITTING,
- TIMED_WAITTING,
- TERMINATED;
- }
先上圖,我們再依次來看。
1. New
A thread that has not yet started is in this state.
就是指線程剛創(chuàng)建,還沒啟動的時候,比如剛 new 了一個 thread。
- MyThread myThread = new MyThread();
2. Runnable
A thread is executing in the Java virtual machine but it may be waiting for other resources from the operating system such as processor.
那么接下來,自然就是要啟動線程了,也就是調(diào)用 thread 的 start() 方法。
- myThread.start();
啟動之后,線程就進入了 Runnable 狀態(tài)。
此時所有的線程都會添加到一個等待隊列里,等待“CPU 調(diào)度”。
如果搶占到 CPU 的資源,那就執(zhí)行;如果沒搶到,就等著唄,等當前正在執(zhí)行的線程完成它能執(zhí)行的時間片之后,再次搶占。
要注意這里在等待的一般是系統(tǒng)資源,而不是鎖或者其他阻塞。
3. Blocked
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 wait() Object.
這里給出了非常明確的 use case,就是被鎖在外面的才叫阻塞。所以這里必須要有至少 2 個線程。
4. Waiting
A thread in the waiting state is waiting for another thread to perform a particular action.
那具體有哪些原因呢?
A thread is in the waiting state due to calling one of the following methods:
- Object.wait with no timeout
- Thread.join with no timeout
- LockSupport.park
所以說,當調(diào)用了
- wait(),
- join(),
- park()
方法之后,線程進入等待狀態(tài)。
這里的等待狀態(tài)是沒有時間限制的,可以無限的等下去... 所以需要有人來喚醒:
如果是通過 wait() 進入等待狀態(tài)的,需要有 notify() 或者 notifyAll() 方法來喚醒;
如果是通過 join() 進入等待狀態(tài)的,需要等待目標線程運行結(jié)束。
比如在生產(chǎn)者消費者模型里,當沒有商品的時候,消費者就需要等待,等待生產(chǎn)者生產(chǎn)好了商品發(fā) notify()。下一篇文章我們會細講。
5. Timed_waiting
導致這個狀態(tài)的原因如下:
- Thread.sleep
- Object.wait with timeout
- Thread.join with timeout
- LockSupport.parkNanos
- LockSupport.parkUntil
其實就是在上一種狀態(tài)的基礎上,給了具體的時間限制。
那么當時間結(jié)束后,線程就解放了。
6. Terminated
A thread that has exited is in this state.
這里有 3 種情況會終止線程:
- 執(zhí)行完所有代碼,正常結(jié)束;
- 強制被結(jié)束,比如調(diào)用了 stop() 方法,現(xiàn)在已經(jīng)被棄用;
- 拋出了未捕獲的異常。
線程一旦死亡就不能復生。
如果在一個死去的線程上調(diào)用 start() 方法,那么程序會拋出 java.lang.IllegalThreadStateException。
接下來我們說說多線程中常用的 11 個 APIs。
APIs
1. join()
join() 方法會強制讓該線程執(zhí)行,并且一直會讓它執(zhí)行完。
比如上一篇文章的例子是兩個線程交替執(zhí)行的,那么我們這里該下,改成調(diào)用小齊線程.join(),那么效果就是先輸出 小齊666。
- public class MyRunnable implements Runnable {
- @Override
- public void run() {
- for(int i = 0; i < 100; i++) {
- System.out.println("小齊666:" + i);
- }
- }
- public static void main(String[] args) throws InterruptedException {
- Thread t = new Thread(new MyRunnable());
- t.start();
- t.join();
- for(int i = 0; i < 100; i++) {
- System.out.println("主線程" + i + ":齊姐666");
- }
- }
- }
所以 join() 能夠保證某個線程優(yōu)先執(zhí)行,而且會一直讓它執(zhí)行完,再回歸到公平競爭狀態(tài)。
join() 方法其實是用 wait() 來實現(xiàn)的,我們來看下這個方法。
2. wait() and notify()
wait() 其實并不是 Thread 類的方法,而是 Object 里面的方法。
該方法就是讓當前對象等待,直到另一個對象調(diào)用 notify() 或者 notifyAll()。
當然了,我們也可以設定一個等待時長,到時間之后對象將會自動蘇醒。
4. yield()
yield 本身的中文意思是屈服,用在這里倒也合適。
yield() 表示當前線程主動讓出 CPU 資源一下,然后我們再一起去搶。
注意這里讓一下真的只是一下,從“執(zhí)行中”回到“等待 CPU 分配資源”,然后所有線程再一起搶占資源。
5. sleep()
顧名思義,這個方法就是讓當前線程睡一會,比如說,
- myThread.sleep(1000); // 睡眠 1 秒鐘
它會拋出一個 InterruptedException 異常,所以還要 try catch 一下。
6. currentThread()
Returns a reference to the currently executing thread object.
該方法是獲取當前線程對象。
注意它是一個 static 方法,所以直接通過 Thread 類調(diào)用。
比如打印當前線程
- System.out.println(Thread.currentThread());
前文的例子中,它會輸出:
- Thread[Thread-0,5,main]
- Thread[main,5,main]
沒錯,它的返回值也是 Thread 類型。
7. getName()
該方法可以獲取當前線程名稱。
這個名稱可以自己設置,比如:
- Thread t = new Thread(new MyRunnable(), "壹齊學");
8. getId()
該方法是獲取線程的 Id.
9. getPriority()
線程也有優(yōu)先級的哦~
雖然優(yōu)先級高的線程并不能百分百保證一定會先執(zhí)行,但它是有更大的概率被先執(zhí)行的。
優(yōu)先級的范圍是 1-10,我們來看源碼:
- /**
- * The minimum priority that a thread can have.
- */
- public final static int MIN_PRIORITY = 1;
- /**
- * The default priority that is assigned to a thread.
- */
- public final static int NORM_PRIORITY = 5;
- /**
- * The maximum priority that a thread can have.
- */
- public final static int MAX_PRIORITY = 10;
如果不在這個范圍,JDK 拋出 IllegalArgumentException() 的異常。
10. setPriority()
當然啦,我們也是可以自己設置某個線程的優(yōu)先級的。
設置的優(yōu)先級也需要在規(guī)定的 1-10 的范圍內(nèi)哦,如果不在這個范圍也會拋異常。
11. stop()
最后我們來說下 stop() 方法,也是前文提到過的強制停止線程的一種方式,但現(xiàn)在已被棄用,因為會引起一些線程安全方面的問題。
好了,以上就是有關線程狀態(tài)和常用 API 的介紹了。相信大家看完之后對線程的整個流程應該有了清晰的認識,其實里面還有很多細節(jié)我沒有展開,畢竟這是多線程的第 2 講,更深入的內(nèi)容我們慢慢來。
本文轉(zhuǎn)載自微信公眾號「 碼農(nóng)田小齊」,可以通過以下二維碼關注。轉(zhuǎn)載本文請聯(lián)系 碼農(nóng)田小齊公眾號。