前言
線程并發(fā)系列文章:
Java 線程基礎(chǔ)
Java 線程狀態(tài)
Java “優(yōu)雅”地中斷線程-實(shí)踐篇
Java “優(yōu)雅”地中斷線程-原理篇
真正理解Java Volatile的妙用
Java ThreadLocal你之前了解的可能有誤
Java Unsafe/CAS/LockSupport 應(yīng)用與原理
Java 并發(fā)"鎖"的本質(zhì)(一步步實(shí)現(xiàn)鎖)
Java Synchronized實(shí)現(xiàn)互斥之應(yīng)用與源碼初探
Java 對(duì)象頭分析與使用(Synchronized相關(guān))
Java Synchronized 偏向鎖/輕量級(jí)鎖/重量級(jí)鎖的演變過(guò)程
Java Synchronized 重量級(jí)鎖原理深入剖析上(互斥篇)
Java Synchronized 重量級(jí)鎖原理深入剖析下(同步篇)
Java并發(fā)之 AQS 深入解析(上)
Java并發(fā)之 AQS 深入解析(下)
Java Thread.sleep/Thread.join/Thread.yield/Object.wait/Condition.await 詳解
Java 并發(fā)之 ReentrantLock 深入分析(與Synchronized區(qū)別)
Java 并發(fā)之 ReentrantReadWriteLock 深入分析
Java Semaphore/CountDownLatch/CyclicBarrier 深入解析(原理篇)
Java Semaphore/CountDownLatch/CyclicBarrier 深入解析(應(yīng)用篇)
最詳細(xì)的圖文解析Java各種鎖(終極篇)
線程池必懂系列
在現(xiàn)代操作系統(tǒng)里烫幕,有進(jìn)程和線程的概念,我們先來(lái)簡(jiǎn)單了解一下這兩個(gè)概念畏浆。
- 進(jìn)程是資源分派的基本單位
- 線程是cpu調(diào)度基本單位
剛開(kāi)始,計(jì)算機(jī)系統(tǒng)比較簡(jiǎn)單面睛,設(shè)計(jì)者把cpu上執(zhí)行的每個(gè)任務(wù)抽象為進(jìn)程纪隙,操作系統(tǒng)統(tǒng)一管理進(jìn)程的調(diào)度舔琅,給每個(gè)進(jìn)程分配地址空間、外設(shè)等吝秕,這些統(tǒng)稱(chēng)為進(jìn)程的資源泊脐。當(dāng)cpu執(zhí)行A進(jìn)程過(guò)程中,發(fā)現(xiàn)A的時(shí)間片已經(jīng)耗盡郭膛,因此先將A進(jìn)程掛起(釋放cpu)晨抡,并保存進(jìn)程A的上下文,然后再調(diào)度進(jìn)程B则剃。后來(lái)發(fā)現(xiàn)每次切換進(jìn)程的上下文都比較耗時(shí)耘柱,浪費(fèi)有限的系統(tǒng)資源,因此將進(jìn)程再劃分為粒度更小的單位棍现,稱(chēng)之為線程(實(shí)際上是cpu上的一個(gè)執(zhí)行流)调煎,進(jìn)程內(nèi)的線程共享進(jìn)程的資源,因此cpu切換線程所花代價(jià)更小己肮。
Linux進(jìn)程狀態(tài)
cpu同一時(shí)刻只能處理一個(gè)進(jìn)程士袄,有可能進(jìn)程還在排隊(duì)等待cpu執(zhí)行悲关,可能在某個(gè)地方等待資源(如輸入事件等),因此操作系統(tǒng)給予進(jìn)程狀態(tài)來(lái)標(biāo)識(shí)進(jìn)程某個(gè)時(shí)刻的狀態(tài)娄柳。[1]
如上圖寓辱,進(jìn)程狀態(tài)為ready(就緒),running(運(yùn)行中)赤拒,waiting(等待)
實(shí)際上線程狀態(tài)也可以類(lèi)比進(jìn)程狀態(tài)秫筏。
Java線程狀態(tài)
Java是門(mén)支持多線程的語(yǔ)言,Java線程是JVM實(shí)現(xiàn)的挎挖,JVM是OS的一個(gè)進(jìn)程这敬,Java線程對(duì)于OS來(lái)說(shuō)是透明的。接下來(lái)我們就Java線程類(lèi)Thread入手了解一下Java線程狀態(tài)蕉朵。
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種狀態(tài)涵蓋了Thread整個(gè)生命周期,簡(jiǎn)單概括一下:
1始衅、NEW:構(gòu)造了thread實(shí)例冷蚂,但是還沒(méi)有start
2、RUNNABLE:線程正在運(yùn)行或者正等待被cpu執(zhí)行
3汛闸、BLOCKED:線程調(diào)用synchronized關(guān)鍵字等待獲取monitor鎖
4帝雇、WAITING:線程調(diào)用了無(wú)超時(shí)的wait、join蛉拙、park方法
5、TIMED_WAITING:線程調(diào)用了有超時(shí)的wait彻亲、sleep孕锄、join、parkNanos苞尝、parkUntil方法
6畸肆、TERMINATED:線程終止/完成了運(yùn)行
線程各種狀態(tài)之間是如何轉(zhuǎn)換的呢?用圖說(shuō)明:[2]
這張圖清晰展示了各個(gè)狀態(tài)之間的關(guān)系宙址,大家可能注意到圖上多了兩個(gè)狀態(tài):Ready和Running轴脐,而少了RUNNABLE狀態(tài)。還記得之前說(shuō)的RUNNABLE:“線程正在運(yùn)行或者正等待被cpu執(zhí)行”抡砂,實(shí)際上RUNNABLE可以認(rèn)為是Ready和Running狀態(tài)的結(jié)合體大咱,Ready表示就緒狀態(tài),Running表示正在運(yùn)行的狀態(tài)注益。
代碼驗(yàn)證狀態(tài)
上面的狀態(tài)描述可能比較抽象碴巾,Java 1.5之后有個(gè)方法:getState() 用來(lái)獲取線程的狀態(tài),因此我們可以打印該值來(lái)觀察狀態(tài)之間的流轉(zhuǎn)丑搔。
驗(yàn)證BLOCKED狀態(tài)
public class TestThreadState {
public static void main(String args[]) {
final Object object = new Object();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (object) {
System.out.println("t1 getted lock");
while(true);
}
}
}, "t1");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (object) {
System.out.println("t2 getted lock");
}
}
}, "t2");
//test state
t1.start();
sleep(2);
t2.start();
while(true) {
System.out.println("t1 state:" + t1.getState());
System.out.println("t2 state:" + t2.getState());
sleep(2);
}
}
private static void sleep(long time) {
try {
TimeUnit.SECONDS.sleep(time);
} catch (Exception e) {
}
}
}
t1先獲得monitor lock厦瓢,并一直循環(huán)不釋放鎖提揍,此時(shí)狀態(tài)為RUNNABLE;t2嘗試獲取monitor lock失敗煮仇,并阻塞劳跃,此時(shí)狀態(tài)為BLOCKED。
需要注意的是浙垫,如果使用ReentrantLock刨仑,線程因ReentrantLock阻塞,此時(shí)線程狀態(tài)為WAITING绞呈。
驗(yàn)證WAITING/TIMED_WAITING狀態(tài)
將上面的例子稍微修改為:
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (object) {
System.out.println("t1 getted lock");
try {
object.wait();
System.out.println("t1 get signal from t2");
} catch (Exception e) {
}
}
}
}, "t1");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (object) {
System.out.println("t2 getted lock");
sleep(3);
System.out.println("t2 notify");
try {
object.notify();
} catch (Exception e) {
}
//先睡眠
sleep(3);
}
}
}, "t2");
t1先獲得鎖贸人,并調(diào)用wait阻塞線程和放棄鎖,此時(shí)狀態(tài)為:WAITING佃声。t2獲取鎖成功艺智,并睡眠3s,此時(shí)狀態(tài)為:TIMED_WAITING圾亏。t2睡眠結(jié)束后調(diào)用notify喚醒t1十拣,此時(shí)t2繼續(xù)睡眠,狀態(tài)為:TIMED_WAITING志鹃。t1收到notify信息后夭问,嘗試獲取鎖,但由于此時(shí)t2還在睡眠沒(méi)有釋放鎖曹铃,因此t1被阻塞缰趋,狀態(tài)為BlOCKED。t2睡眠結(jié)束陕见,釋放鎖秘血,t1獲得鎖并接著打印“t1 get signal from t2”語(yǔ)句,最終t1评甜、t2終止了運(yùn)行灰粮,處在TERMINATED狀態(tài)。
Join和Yield
Join是Thread類(lèi)成員方法忍坷,該方法作用是當(dāng)前線程等待某個(gè)線程結(jié)束后再繼續(xù)執(zhí)行后續(xù)代碼粘舟。
Yield是Thread類(lèi)靜態(tài)方法,該方法作用是讓當(dāng)前線程放棄cpu佩研,并等待cpu重新調(diào)度(當(dāng)前線程可能再次獲得cpu)柑肴。通俗點(diǎn)的說(shuō)法是給其它線程一個(gè)機(jī)會(huì),讓大家回到同一起跑線韧骗,至于誰(shuí)被cpu選中嘉抒,看個(gè)人造化。
public class TestJoin {
public static void main(String args[]) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(3000);
System.out.println("t1 end");
} catch (Exception e) {
}
}
}, "t1");
t1.start();
try {
System.out.println("wait t1 terminate");
t1.join();
} catch (Exception e) {
}
System.out.println("main thread end");
}
}
main thread等待t1執(zhí)行完成再繼續(xù)執(zhí)行袍暴。
網(wǎng)上不少文章列舉Yield用法示例:
public class TestYield {
public static void main(String args[]) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
if (i == 3) {
Thread.yield();
}
System.out.println("t1->" + i);
}
}
}, "t1");
t1.start();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("t2->" + i);
}
}
}, "t2");
t2.start();
}
}
t1在執(zhí)行期間調(diào)用yield()方法些侍,放棄cpu隶症,這時(shí)候t2就可以拿到cpu。實(shí)際上打印結(jié)果的順序充滿不確定性岗宣,不能完全確定是由于yield()方法引起的差異蚂会,因此上面的例子不夠充分。而我們平時(shí)使用yield()方法的情況很少耗式,基本無(wú)需關(guān)注胁住。
LockSupport
用于線程阻塞與喚醒,更詳細(xì)的解釋請(qǐng)查看:Java Unsafe/CAS/LockSupport 應(yīng)用與原理
線程狀態(tài)的意義
在開(kāi)發(fā)的過(guò)程中刊咳,免不了用到多線程彪见。多線程之間的協(xié)作(同步、互斥)需要正確有序的進(jìn)行娱挨,在程序運(yùn)行過(guò)程中如果出現(xiàn)了多線程問(wèn)題一般都比較隨機(jī)不容易復(fù)現(xiàn)(死鎖等)余指,這個(gè)時(shí)候可以通過(guò)打印線程狀態(tài)(jstack)來(lái)分析各個(gè)線程狀態(tài),到底處在什么狀態(tài)跷坝?因?yàn)槭裁丛蛱幵谶@個(gè)狀態(tài)酵镜?實(shí)際應(yīng)該進(jìn)行到哪個(gè)狀態(tài)?這樣對(duì)于問(wèn)題的分析就會(huì)做到有的放矢柴钻,提高效率淮韭。
對(duì)如何中斷線程感興趣的同學(xué)請(qǐng)移步:Java “優(yōu)雅”地中斷線程
參考文章
[1] https://www.tecmint.com/linux-process-management/
[2] https://www.uml-diagrams.org/examples/java-6-thread-state-machine-diagram-example.html