前言
本文將對(duì) Java 線程 Thread 進(jìn)行學(xué)習(xí)和總結(jié),以下是概覽:
一唠摹、Thread 創(chuàng)建
線程的創(chuàng)建主要依靠實(shí)現(xiàn) Runnable
接口。調(diào)用 start()
方法使線程進(jìn)入就緒狀態(tài)奉瘤,等待 CPU
調(diào)度勾拉,然后 run ()
方法由 JVM
調(diào)用。
1.1 實(shí)現(xiàn) Runnable
public interface Runnable {
public abstract void run();
}
例子
public class TestThread implements Runnable{
@Override
public void run() {
System.out.println("TestThread Running");
}
}
使用
public static void main(String[] args) {
// 創(chuàng)建線程
TestThread testThread = new TestThread();
// 將 Runnable 對(duì)象作為參數(shù)傳遞給 Thread
Thread thread = new Thread(testThread);
thread.start();
}
為什么不能直接創(chuàng)建 TestThread
對(duì)象并調(diào)用 run()
方法呢盗温,因?yàn)檫@樣只是普通地調(diào)用了對(duì)象的方法藕赞,并沒有經(jīng)歷創(chuàng)建線程的過程。
所以還是需要利用 Java 為我們寫好的 Thread
類對(duì)線程對(duì)象(實(shí)現(xiàn)了 Runnable
的對(duì)象)進(jìn)行一次包裝卖局,然后由 Thread
類替我們完成創(chuàng)建并執(zhí)行線程的過程斧蜕。
1.2 繼承 Thread
Thread
類本身實(shí)現(xiàn)了 Runnable
接口,內(nèi)部又包裝了一個(gè) Thread
對(duì)象 target
砚偶,執(zhí)行 run
方法的時(shí)候?qū)嶋H調(diào)用 target
的 run
方法批销。
public class Thread implements Runnable {
private Runnable target;
...
public void run() {
if (target != null) {
target.run();
}
}
}
所以本質(zhì)上還是實(shí)現(xiàn)了 Runnable
接口實(shí)現(xiàn)線程的創(chuàng)建。
二染坯、部分屬性
Thread
類的部分屬性:
// 對(duì)象鎖均芽,用于使當(dāng)前線程占有 CPU
private final Object lock = new Object();
// 該值不等于0,說明線程存活酒请,尚不知原理
private volatile long nativePeer;
// 線程名稱
private volatile String name;
// 線程優(yōu)先級(jí)
private int priority;
// 是否守護(hù)線程
private boolean daemon = false;
接下來將逐個(gè)分析。
2.1 lock 鎖對(duì)象
lock
對(duì)象用于加鎖和使調(diào)用者線程進(jìn)入阻塞狀態(tài)鸣个。
阻塞
調(diào)用 join
方法會(huì)使當(dāng)前線程獲取 lock
對(duì)象的鎖羞反,然后 lock
對(duì)象執(zhí)行 wait
方法進(jìn)入阻塞狀態(tài),實(shí)現(xiàn)阻塞當(dāng)前線程的效果囤萤。
public final void join() throws InterruptedException {
// 傳入 0 表示無限阻塞
join(0);
}
public final void join(long millis) throws InterruptedException {
synchronized(lock) { // 同步鎖
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) { // 等于 0 無限阻塞
while (isAlive()) {
lock.wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
lock.wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
}
比如現(xiàn)在有個(gè)線程 Thread1
昼窗,在運(yùn)行過程中需要執(zhí)行 Thread2
的相關(guān)邏輯。Thread2
在 Thread1
的 run()
方法中調(diào)用了 join()
方法涛舍,這樣 Thread1
就獲取到了 Thread2
中 lock
對(duì)象的鎖澄惊。
lock
對(duì)象循環(huán)調(diào)用 wait()
方法進(jìn)行阻塞。因?yàn)檫@時(shí)是處于 Thread1
的運(yùn)行環(huán)境下富雅,并且由 synchronized 獲取了 monitor(相當(dāng)于同步鎖)掸驱,所以 Thread1
進(jìn)入了阻塞狀態(tài)。
注意:
當(dāng)前線程必須是此對(duì)象的監(jiān)視器所有者没佑,否則還是會(huì)發(fā)生 IllegalMonitorStateException 異常毕贼。
如果當(dāng)前線程在等待之前或在等待時(shí)被任何線程中斷,則會(huì)拋出 InterruptedException 異常蛤奢。
解除阻塞
解除阻塞有兩種方式鬼癣,但不限于這兩種陶贼。一種是 超時(shí) 自動(dòng)釋放,另一種是由 JVM 釋放待秃。
-
join()
方法傳入millis (毫秒數(shù))
拜秧,在時(shí)間達(dá)到之后跳出循環(huán),lock
對(duì)象不再加鎖章郁。這樣Thread1
持有的lock
對(duì)象不再阻塞枉氮,由此該線程回到就緒狀態(tài)。 - 加入線程執(zhí)行完畢驱犹,JVM 釋放鎖嘲恍。線程死亡時(shí) JVM 會(huì)調(diào)用
lock
對(duì)象的notify_all()
方法來釋放所有鎖。
以下代碼摘自 Thread.join的作用和原理 中的 hotspot 虛擬機(jī)源碼:
void JavaThread::exit(bool destroy_vm, ExitType exit_type) {
assert(this == JavaThread::current(), "thread consistency check");
...
// Notify waiters on thread object. This has to be done after exit() is called
// on the thread (if the thread is the last thread in a daemon ThreadGroup the
// group should have the destroyed bit set before waiters are notified).
ensure_join(this);
assert(!this->has_pending_exception(), "ensure_join should have cleared");
...
可以看到線程退出方法 exit()
調(diào)用了 ensure_join(this)
釋放鎖:
static void ensure_join(JavaThread* thread) {
// We do not need to grap the Threads_lock, since we are operating on ourself.
Handle threadObj(thread, thread->threadObj());
assert(threadObj.not_null(), "java thread object must exist");
ObjectLocker lock(threadObj, thread);
// Ignore pending exception (ThreadDeath), since we are exiting anyway
thread->clear_pending_exception();
// Thread is exiting. So set thread_status field in java.lang.Thread class to TERMINATED.
java_lang_Thread::set_thread_status(threadObj(), java_lang_Thread::TERMINATED);
// Clear the native thread instance - this makes isAlive return false and allows the join()
// to complete once we've done the notify_all below
//這里是清除native線程雄驹,這個(gè)操作會(huì)導(dǎo)致isAlive()方法返回false
java_lang_Thread::set_thread(threadObj(), NULL);
lock.notify_all(thread);//注意這里
// Ignore pending exception (ThreadDeath), since we are exiting anyway
thread->clear_pending_exception();
}
這里的 lock
對(duì)象大概就是 Thread
中的同步鎖對(duì)象了佃牛,調(diào)用 Java Object notifyAll() 方法 用來喚醒所有持有該對(duì)象鎖的線程。
2.2 其它參數(shù)
- nativePeer: 判斷線程是否存活依靠此字段医舆,可能在線程銷毀的時(shí)候把該值置為 0俘侠。
public final boolean isAlive() {
return nativePeer != 0;
}
-
priority: 優(yōu)先級(jí),默認(rèn)
NORM_PRIORITY=5
蔬将。最小MIN_PRIORITY = 1
爷速,最大MAX_PRIORITY = 10
。
設(shè)置該參數(shù)的作用在于希望較高優(yōu)先級(jí)的線程可以先行執(zhí)行霞怀,但實(shí)際可能并非總是如此惫东。
優(yōu)先級(jí)和操作系統(tǒng)及虛擬機(jī)版本相關(guān)。
優(yōu)先級(jí)只是代表告知了 「線程調(diào)度器」該線程的重要度有多大毙石。如果有大量線程都被堵塞廉沮,都在等候運(yùn)
行,調(diào)試程序會(huì)首先運(yùn)行具有最高優(yōu)先級(jí)的那個(gè)線程徐矩。然而滞时,這并不表示優(yōu)先級(jí)較低的線程不會(huì)運(yùn)行(換言之,不會(huì)因?yàn)榇嬖趦?yōu)先級(jí)而導(dǎo)致死鎖)滤灯。若線程的優(yōu)先級(jí)較低坪稽,只不過表示它被準(zhǔn)許運(yùn)行的機(jī)會(huì)小一些而已。
- daemon: 是否為守護(hù)線程鳞骤。守護(hù)線程就像一個(gè)衛(wèi)士窒百,追隨用戶線程。當(dāng)用戶線程銷毀時(shí)豫尽,守護(hù)線程也就沒有了意義贝咙,可能會(huì)被隨時(shí)回收柔袁。
三喧伞、Thread 狀態(tài)
3.1 線程的幾種狀態(tài)
有關(guān)線程狀態(tài)纸泡,大概有兩種說法。
- 一種是概括性的五種狀態(tài):
- 創(chuàng)建狀態(tài) 已經(jīng)創(chuàng)建出對(duì)象蔼水,并未調(diào)用 start震糖;
- 就緒狀態(tài) 調(diào)用 start 方法之后,并未開始運(yùn)行趴腋;
- 運(yùn)行狀態(tài) 被 cpu 執(zhí)行吊说,run 方法被調(diào)用;
- 阻塞狀態(tài) 在運(yùn)行過程中被暫停优炬,調(diào)用 wait() 或被別的線程 join() 等颁井;
- 死亡狀態(tài) run 方法執(zhí)行結(jié)束,或者調(diào)用 stop() 結(jié)束運(yùn)行蠢护。
- 另一種是根據(jù)源碼的六種狀態(tài):
- NEW 剛 new 出來雅宾;
- RUNNABLE 運(yùn)行中;
- BLOCKED 等待同步鎖葵硕;
- WAITING 阻塞狀態(tài)眉抬,wait() 或被 join();
- TIMED_WAITING 阻塞狀態(tài)懈凹,限時(shí)蜀变;
- TERMINATED 被終止。
個(gè)人感覺沒有必要糾結(jié)孰對(duì)孰錯(cuò)介评,這是兩個(gè)層面上的理解库北。前者更傾向于總覽,后者更為細(xì)化们陆。
3.2 線程狀態(tài)控制
線程調(diào)度
簡(jiǎn)單地說就是設(shè)置優(yōu)先級(jí)寒瓦,使 JVM 進(jìn)行協(xié)調(diào)。避免多個(gè)線程搶奪有限資源造成的死機(jī)或者崩潰棒掠。上文已經(jīng)提到過孵构,最低優(yōu)先級(jí) MIN_PRIORITY =1
屁商、最高優(yōu)先級(jí) MAX_PRIORITY = 10
烟很,默認(rèn)優(yōu)先級(jí) NORM_PRIORITY=5
。
為了控制線程的運(yùn)行策略蜡镶,Java定義了線程調(diào)度器來監(jiān)控系統(tǒng)中處于就緒狀態(tài)的所有線程雾袱。線程調(diào)度器按照線程的優(yōu)先級(jí)決定那個(gè)線程投入處理器運(yùn)行。在多個(gè)線程處于就緒狀態(tài)的條件下官还,具有高優(yōu)先級(jí)的線程會(huì)在低優(yōu)先級(jí)線程之前得到執(zhí)行芹橡。
守護(hù)線程
特殊的低優(yōu)先級(jí)守護(hù)(Daemon)線程,它是為系統(tǒng)中的其它對(duì)象或線程服務(wù)望伦。
典型的守護(hù)線程例子是JVM中的系統(tǒng)資源自動(dòng)回收線程林说,它始終在低級(jí)別的狀態(tài)中運(yùn)行煎殷,用于實(shí)時(shí)監(jiān)控和管理系統(tǒng)中的可回收資源。
當(dāng)所有用戶線程銷毀后腿箩,也不會(huì)產(chǎn)生垃圾了豪直。守護(hù)線程會(huì)隨著 JVM 銷毀。
線程分組
Java定義了在多線程運(yùn)行系統(tǒng)中的線程組(ThreadGroup)對(duì)象珠移,用于實(shí)現(xiàn)按照特定功能對(duì)線程進(jìn)行集中式分組管理弓乙。
用戶創(chuàng)建的每個(gè)線程均屬于某線程組,這個(gè)線程組可以在線程創(chuàng)建時(shí)指定钧惧,也可以不指定線程組以使該線程處于默認(rèn)的線程組之中暇韧。但是,一旦線程加入某線程組浓瞪,該線程就一直存在于該線程組中直至線程死亡懈玻,不能在中途改變線程所屬的線程組。
與線程類似追逮,可以針對(duì)線程組對(duì)象進(jìn)行線程組的調(diào)度酪刀、狀態(tài)管理以及優(yōu)先級(jí)設(shè)置等。在對(duì)線程組進(jìn)行管理過程中钮孵,加入到某線程組中的所有線程均被看作統(tǒng)一的對(duì)象骂倘。
四、Thread 其它
4.1 線程同步
- synchronized: 依賴 JVM 實(shí)現(xiàn)巴席,通過鎖住 對(duì)象历涝、方法、類 等實(shí)現(xiàn)線程同步漾唉;
- volatile: 特殊域變量荧库,使用該關(guān)鍵字修飾對(duì)象,保證其 可見性赵刑。使其無論在哪個(gè)線程讀取時(shí)都從內(nèi)存重新讀取分衫,而不是在 CPU 緩存中讀取般此;
- 重入鎖: ReentrantLock 可重入蚪战,內(nèi)含 CAS+AQS 機(jī)制。CAS 保證數(shù)據(jù)準(zhǔn)確性铐懊,AQS 保證順序邀桑。
- ThreadLocal: 為每一條線程創(chuàng)建數(shù)據(jù)副本,這樣各個(gè)線程間處理的數(shù)據(jù)互不影響科乎。因?yàn)樘幚淼牟皇峭粩?shù)據(jù)壁畸,要注意數(shù)據(jù)的一致性。
4.2 終止線程
終止線程三種方法:
- 設(shè)置 flag 標(biāo)記:為 false 時(shí)結(jié)束代碼運(yùn)行,JVM 會(huì)回收掉線程捏萍;
- stop 方法(不推薦):強(qiáng)行結(jié)束線程太抓,但是可能會(huì)因馬上釋放鎖造成數(shù)據(jù)產(chǎn)生誤差;
- interrupt 方法:使線程進(jìn)入中斷狀態(tài)令杈,代碼邏輯中 catch InterruptedException 用來結(jié)束邏輯執(zhí)行腻异。
總結(jié)
線程使用時(shí)要特別注意同步、鎖的使用这揣。由于多個(gè)線程不易管理悔常,實(shí)際使用時(shí)一般用 線程池 進(jìn)行處理。后面講對(duì)線程池原理進(jìn)行記錄给赞。
參考資料