10 張圖聊聊線程的生命周期和常用 APIs
今天我們來說一下線程的生命周期和常用 APIs:我們需要非常清楚的知道線程的各種狀態(tài),比如排查程序運(yùn)行慢的原因時(shí),就需要看下是不是哪里被阻塞了;另外它也是面試時(shí)非常喜歡問的,如果基礎(chǔ)內(nèi)容都答不好,恐怕直接就掛了。
本文分為兩大部分,
1.線程的 6 大狀態(tài);
2.多線程常用的 APIs:
- join()
- wait()
- notify()
- yield()
- sleep()
- currentThread()
- getName()
- getId()
- getPriority()
- setPriority()
- stop()
線程狀態(tài)
關(guān)于線程的狀態(tài),網(wǎng)上各種說法都有,比較流行的是 5 種或者 6 種。關(guān)于 5 種狀態(tài)的那個(gè)版本我沒有找到理論依據(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)建,還沒啟動(dòng)的時(shí)候,比如剛 new 了一個(gè) 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.
那么接下來,自然就是要啟動(dòng)線程了,也就是調(diào)用 thread 的 start() 方法。
- myThread.start();
啟動(dòng)之后,線程就進(jìn)入了 Runnable 狀態(tài)。
此時(shí)所有的線程都會(huì)添加到一個(gè)等待隊(duì)列里,等待“CPU 調(diào)度”。
如果搶占到 CPU 的資源,那就執(zhí)行;如果沒搶到,就等著唄,等當(dāng)前正在執(zhí)行的線程完成它能執(zhí)行的時(shí)間片之后,再次搶占。
要注意這里在等待的一般是系統(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 個(gè)線程。
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
所以說,當(dāng)調(diào)用了
- wait(),
- join(),
- park()
方法之后,線程進(jìn)入等待狀態(tài)。
這里的等待狀態(tài)是沒有時(shí)間限制的,可以無限的等下去... 所以需要有人來喚醒:
如果是通過 wait() 進(jìn)入等待狀態(tài)的,需要有 notify() 或者 notifyAll() 方法來喚醒;
如果是通過 join() 進(jìn)入等待狀態(tài)的,需要等待目標(biāo)線程運(yùn)行結(jié)束。
比如在生產(chǎn)者消費(fèi)者模型里,當(dāng)沒有商品的時(shí)候,消費(fèi)者就需要等待,等待生產(chǎn)者生產(chǎn)好了商品發(fā) notify()。下一篇文章我們會(huì)細(xì)講。
5. Timed_waiting
導(dǎo)致這個(gè)狀態(tài)的原因如下:
- Thread.sleep
- Object.wait with timeout
- Thread.join with timeout
- LockSupport.parkNanos
- LockSupport.parkUntil
其實(shí)就是在上一種狀態(tài)的基礎(chǔ)上,給了具體的時(shí)間限制。
那么當(dāng)時(shí)間結(jié)束后,線程就解放了。
6. Terminated
A thread that has exited is in this state.
這里有 3 種情況會(huì)終止線程:
- 執(zhí)行完所有代碼,正常結(jié)束;
- 強(qiáng)制被結(jié)束,比如調(diào)用了 stop() 方法,現(xiàn)在已經(jīng)被棄用;
- 拋出了未捕獲的異常。
線程一旦死亡就不能復(fù)生。
如果在一個(gè)死去的線程上調(diào)用 start() 方法,那么程序會(huì)拋出 java.lang.IllegalThreadStateException。
接下來我們說說多線程中常用的 11 個(gè) APIs。
APIs
1. join()
join() 方法會(huì)強(qiáng)制讓該線程執(zhí)行,并且一直會(huì)讓它執(zhí)行完。
比如上一篇文章的例子是兩個(gè)線程交替執(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() 能夠保證某個(gè)線程優(yōu)先執(zhí)行,而且會(huì)一直讓它執(zhí)行完,再回歸到公平競爭狀態(tài)。
join() 方法其實(shí)是用 wait() 來實(shí)現(xiàn)的,我們來看下這個(gè)方法。
2. wait() and notify()
wait() 其實(shí)并不是 Thread 類的方法,而是 Object 里面的方法。
該方法就是讓當(dāng)前對(duì)象等待,直到另一個(gè)對(duì)象調(diào)用 notify() 或者 notifyAll()。
當(dāng)然了,我們也可以設(shè)定一個(gè)等待時(shí)長,到時(shí)間之后對(duì)象將會(huì)自動(dòng)蘇醒。
4. yield()
yield 本身的中文意思是屈服,用在這里倒也合適。
yield() 表示當(dāng)前線程主動(dòng)讓出 CPU 資源一下,然后我們再一起去搶。
注意這里讓一下真的只是一下,從“執(zhí)行中”回到“等待 CPU 分配資源”,然后所有線程再一起搶占資源。
5. sleep()
顧名思義,這個(gè)方法就是讓當(dāng)前線程睡一會(huì),比如說,
- myThread.sleep(1000); // 睡眠 1 秒鐘
它會(huì)拋出一個(gè) InterruptedException 異常,所以還要 try catch 一下。
6. currentThread()
Returns a reference to the currently executing thread object.
該方法是獲取當(dāng)前線程對(duì)象。
注意它是一個(gè) static 方法,所以直接通過 Thread 類調(diào)用。
比如打印當(dāng)前線程
- System.out.println(Thread.currentThread());
前文的例子中,它會(huì)輸出:
- Thread[Thread-0,5,main]
- Thread[main,5,main]
沒錯(cuò),它的返回值也是 Thread 類型。
7. getName()
該方法可以獲取當(dāng)前線程名稱。
這個(gè)名稱可以自己設(shè)置,比如:
- Thread t = new Thread(new MyRunnable(), "壹齊學(xué)");
8. getId()
該方法是獲取線程的 Id.
9. getPriority()
線程也有優(yōu)先級(jí)的哦~
雖然優(yōu)先級(jí)高的線程并不能百分百保證一定會(huì)先執(zhí)行,但它是有更大的概率被先執(zhí)行的。
優(yōu)先級(jí)的范圍是 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;
如果不在這個(gè)范圍,JDK 拋出 IllegalArgumentException() 的異常。
10. setPriority()
當(dāng)然啦,我們也是可以自己設(shè)置某個(gè)線程的優(yōu)先級(jí)的。
設(shè)置的優(yōu)先級(jí)也需要在規(guī)定的 1-10 的范圍內(nèi)哦,如果不在這個(gè)范圍也會(huì)拋異常。
11. stop()
最后我們來說下 stop() 方法,也是前文提到過的強(qiáng)制停止線程的一種方式,但現(xiàn)在已被棄用,因?yàn)闀?huì)引起一些線程安全方面的問題。
好了,以上就是有關(guān)線程狀態(tài)和常用 API 的介紹了。相信大家看完之后對(duì)線程的整個(gè)流程應(yīng)該有了清晰的認(rèn)識(shí),其實(shí)里面還有很多細(xì)節(jié)我沒有展開,畢竟這是多線程的第 2 講,更深入的內(nèi)容我們慢慢來。
本文轉(zhuǎn)載自微信公眾號(hào)「 碼農(nóng)田小齊」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系 碼農(nóng)田小齊公眾號(hào)。