對(duì)于線程Thread類(lèi)的使用花椭,可以說(shuō)是java語(yǔ)言必備阿浓,但你是否真正意義上去剖析過(guò)他的內(nèi)部結(jié)構(gòu)堕战,本文從概述的幾個(gè)問(wèn)題出發(fā)补君,一起進(jìn)行源碼閱讀(本文基于Android-27中的Thread源碼)
概述
對(duì)常用的Thread做一次源碼剖析引几,更好的去理解和使用它,看完之后你會(huì)明白的幾個(gè)問(wèn)題:
- 調(diào)用start發(fā)生了什么挽铁?多次調(diào)用start會(huì)怎么樣伟桅?
- start和run方法的區(qū)別
- join和sleep的區(qū)別
- 什么是守護(hù)進(jìn)程
一、創(chuàng)建使用
1. 初始化
Thread構(gòu)造函數(shù)
內(nèi)部調(diào)用--->init()方法
java.lang.Thread#Thread()
java.lang.Thread#Thread(java.lang.Runnable)
java.lang.Thread#Thread(java.lang.ThreadGroup, java.lang.Runnable)
java.lang.Thread#Thread(java.lang.String)
java.lang.Thread#Thread(java.lang.ThreadGroup, java.lang.String)
java.lang.Thread#Thread(java.lang.ThreadGroup, java.lang.String, int, boolean)
java.lang.Thread#Thread(java.lang.Runnable, java.lang.String)
java.lang.Thread#Thread(java.lang.ThreadGroup, java.lang.Runnable, java.lang.String)
java.lang.Thread#Thread(java.lang.ThreadGroup, java.lang.Runnable, java.lang.String, long)
init()方法指定四個(gè)參數(shù):ThreadGroup叽掘,任務(wù)runable楣铁,線程名稱(chēng),棧大小更扁,其中部分參數(shù)初始值都是繼承父線程的屬性
private void init(ThreadGroup g, Runnable target, String name, long stackSize) {
Thread parent = currentThread();//獲取創(chuàng)建thread的線程
if (g == null) {
g = parent.getThreadGroup();
}
g.addUnstarted();//在ThreadGroup中標(biāo)記增加了一個(gè)未啟動(dòng)的線程盖腕,里面操作很簡(jiǎn)單,nUnstartedThreads++;
this.group = g;
this.target = target;
this.priority = parent.getPriority();//繼承父線程的等級(jí)
this.daemon = parent.isDaemon();//繼承父線程的屬性:是否為守護(hù)進(jìn)程
setName(name);
init2(parent);//保存一些常量參數(shù)浓镜,如上溃列,給子線程調(diào)用
/* Stash the specified stack size in case the VM cares */
this.stackSize = stackSize;
tid = nextThreadID();
}
...
//線程 tid遞增一個(gè)
private static synchronized long nextThreadID() {
return ++threadSeqNumber;
}
2. start方法
-
在Android中,檢測(cè)到再次調(diào)用start線程會(huì)拋出IllegalThreadStateException
public synchronized void start() { // Android-changed: throw if 'started' is true if (threadStatus != 0 || started) throw new IllegalThreadStateException(); //還記得上面init方法中膛薛,調(diào)用addUnstarted時(shí)哭廉,標(biāo)記增加了未啟動(dòng)線程 //這里調(diào)用add方法,將線程添加到系統(tǒng)線程數(shù)組相叁,并且將未啟動(dòng)線程數(shù)減一遵绰,相當(dāng)于移出 group.add(this); started = false; try { nativeCreate(this, stackSize, daemon); //調(diào)用native方法啟動(dòng)線程,如果報(bào)錯(cuò)增淹,則直接跳到finally執(zhí)行椿访,started為false, //啟動(dòng)失敗,從group中移除虑润,同時(shí)group中未啟動(dòng)線程數(shù)++ started = true; } finally { try { if (!started) { group.threadStartFailed(this); } } catch (Throwable ignore) { /* do nothing. If start0 threw a Throwable then it will be passed up the call stack */ } } }
3. run方法
//Thread實(shí)現(xiàn)的Runnable接口
class Thread implements Runnable {
...
//調(diào)用傳入的Runnable的run方法
@Override
public void run() {
if (target != null) {
target.run();
}
}
二成玫、Thread阻塞
1.join方法
join方法用于等待線程執(zhí)行完成,傳入的時(shí)間單位為等待的最大時(shí)長(zhǎng),里面是一個(gè) while (isAlive())循環(huán)函數(shù)哭当,當(dāng)不傳入時(shí)間參數(shù)猪腕,則為永久等待直到線程結(jié)束,傳入時(shí)間參數(shù)钦勘,當(dāng)時(shí)間到達(dá)時(shí)會(huì)結(jié)束join方法
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) {
while (isAlive()) {
lock.wait(0);
}
} else {
//循環(huán)陋葡,當(dāng)達(dá)到最大等待時(shí)常,則跳出循環(huán)
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
lock.wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
}
public final void join() throws InterruptedException {
join(0);
}
//等待多少毫秒在加多少納秒
public final void join(long millis, int nanos)
throws InterruptedException {
synchronized(lock) {
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}
if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
millis++;
}
join(millis);
}
}
2.sleep方法
sleep作用是使當(dāng)前線程睡眠指定時(shí)間彻采,其中幾個(gè)關(guān)鍵點(diǎn)
獲取當(dāng)前調(diào)用線程的lock:currentThread().lock;
-
通過(guò)while (true)循環(huán)sleep當(dāng)前線程腐缤,并檢測(cè)睡眠時(shí)間達(dá)到傳輸參數(shù)時(shí)間,break當(dāng)前循環(huán)
public static void sleep(long millis, int nanos)throws InterruptedException { if (millis < 0) { throw new IllegalArgumentException("millis < 0: " + millis); } if (nanos < 0) { throw new IllegalArgumentException("nanos < 0: " + nanos); } if (nanos > 999999) { throw new IllegalArgumentException("nanos > 999999: " + nanos); } //當(dāng)睡眠時(shí)間為0肛响,先檢測(cè)線程是否已經(jīng)中斷岭粤,是的話拋出異常,否則直接return if (millis == 0 && nanos == 0) { // ...but we still have to handle being interrupted. if (Thread.interrupted()) { throw new InterruptedException(); } return; } long start = System.nanoTime(); long duration = (millis * NANOS_PER_MILLI) + nanos; 獲取當(dāng)前線程的lock Object lock = currentThread().lock; // Wait may return early, so loop until sleep duration passes. synchronized (lock) { while (true) { sleep(lock, millis, nanos); long now = System.nanoTime(); long elapsed = now - start; if (elapsed >= duration) { break; } duration -= elapsed; start = now; millis = duration / NANOS_PER_MILLI; nanos = (int) (duration % NANOS_PER_MILLI); } } } public static void sleep(long millis) throws InterruptedException { Thread.sleep(millis, 0); } @FastNative private static native void sleep(Object lock, long millis, int nanos) throws InterruptedException;
3.sleep與join的區(qū)別
- join里面調(diào)用的wait方法特笋,wait方法可以釋放鎖剃浇,而sleep方法是持有鎖
- join(0)是一直等待線程執(zhí)行完成,只有這個(gè)線程執(zhí)行完后猎物,才能執(zhí)行其他線程虎囚,中間通過(guò)循環(huán)lock.wait(delay)實(shí)現(xiàn),它是非靜態(tài)方法霸奕,
- sleep是靜態(tài)方法,通過(guò)currentThread獲取當(dāng)前線程的lock吉拳,它只能作用當(dāng)前線程
三质帅、Thread終止
1.stop方法
stop方法以及被棄用,強(qiáng)行調(diào)用的話會(huì)拋出UnsupportedOperationException異常
@Deprecated
public final void stop() {
stop(new ThreadDeath());
}
@Deprecated
public final void stop(Throwable obj) {
throw new UnsupportedOperationException();
}
2.interrupt方法
部分內(nèi)容引用一篇很詳細(xì)的文章留攒,戳-->《Java線程源碼解析之interrupt》
interrupt的作用是中斷線程煤惩,我們經(jīng)常調(diào)用,interrupt的使用有幾個(gè)注意點(diǎn)
當(dāng)線程處于wait,sleep,join等方法阻塞狀態(tài)時(shí)炼邀,它會(huì)清除當(dāng)前阻塞狀態(tài)魄揉,并拋出InterruptedException異常
在I/O通訊狀態(tài)中調(diào)用interrupt,數(shù)據(jù)通道會(huì)被關(guān)閉拭宁,并將線程狀態(tài)標(biāo)記為中斷洛退,并拋出ClosedByInterruptException異常
如果在java.nio.channels.Selector上堵塞,會(huì)標(biāo)記中斷狀態(tài)杰标,并馬上返回select方法
Lock.lock()方法不會(huì)響應(yīng)中斷兵怯,Lock.lockInterruptibly()方法則會(huì)響應(yīng)中斷并拋出異常,區(qū)別在于park()等待被喚醒時(shí)lock會(huì)繼續(xù)執(zhí)行park()來(lái)等待鎖腔剂,而 lockInterruptibly會(huì)拋出異常
synchronized被喚醒后會(huì)嘗試獲取鎖媒区,失敗則會(huì)通過(guò)循環(huán)繼續(xù)park()等待,因此實(shí)際上是不會(huì)被interrupt()中斷的;
一般情況下,拋出異常時(shí)袜漩,會(huì)清空Thread的interrupt狀態(tài)绪爸,在編程時(shí)需要注意;
//用來(lái)中斷的IO通訊對(duì)象宙攻,在調(diào)用interrupt方法后會(huì)調(diào)用blocker的中斷方法
private volatile Interruptible blocker;
public void interrupt() {
if (this != Thread.currentThread())
checkAccess();
synchronized (blockerLock) {
Interruptible b = blocker;
if (b != null) {
nativeInterrupt();
b.interrupt(this);
return;
}
}
nativeInterrupt();
}
四奠货、線程的狀態(tài)
public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
public State getState() {
// get current thread state
return State.values()[nativeGetStatus(started)];
}
- NEW:線程創(chuàng)建還未啟動(dòng)時(shí)狀態(tài)
- RUNNABLE:線程運(yùn)行狀態(tài),包括一些系統(tǒng)資源等待粘优,如:IO等待仇味,CPU時(shí)間片切換等
- BLOCKED:正在等待monitor lock的狀態(tài),比如:1. 即將進(jìn)入synchronized方法或者塊前等待獲取鎖的這個(gè)臨界時(shí)期狀態(tài)雹顺。2.調(diào)用wait方法釋放鎖之后再次進(jìn)入synchronized方法或者塊前的臨界狀態(tài)
- WAITING:基于上個(gè)BLOCKED狀態(tài)來(lái)說(shuō)丹墨,WAITING就是拿到鎖了,處于wait過(guò)程中的狀態(tài)嬉愧,注意它是特指無(wú)限期的等待贩挣,也就是join()或者wait()等,它是join或者直接wait方法當(dāng)獲取到lock執(zhí)行后,處于等待notify的WAITING狀態(tài)没酣。
- TIMED_WAITING:與上面WAITING相對(duì)王财,WAITING是指無(wú)限期的等待,TIMED_WAITING就是有限期的等待狀態(tài)裕便,包括join(long),wait(long),sleep(long)等绒净。
-
TERMINATED:線程執(zhí)行完成,run結(jié)束的狀態(tài)
image
五偿衰、總結(jié):回答上述問(wèn)題
- 調(diào)用2次start時(shí)挂疆,看start源碼中,里面判斷如果當(dāng)前線程狀態(tài)和是否啟動(dòng)標(biāo)記下翎,
if (threadStatus != 0 || started)
缤言,如果已經(jīng)啟動(dòng)則拋出IllegalThreadStateException異常,可以通過(guò)繼承Thread類(lèi)或者實(shí)現(xiàn)Runnable去開(kāi)啟線程视事,這樣每次new了新的對(duì)象啟動(dòng)線程 - start是啟動(dòng)當(dāng)前Thread線程胆萧,Thread實(shí)現(xiàn)了Runnable接口的run方法,當(dāng)線程啟動(dòng)俐东,run方法會(huì)被調(diào)用跌穗,Thread里面的Run會(huì)調(diào)用傳入Runnable Target的run方法,達(dá)到實(shí)現(xiàn)我們自定義任務(wù)的目的虏辫。如果沒(méi)有傳入Runnable參數(shù)則do nothing
- join是等待線程執(zhí)行完成瞻离,方法通過(guò)內(nèi)部一個(gè)while(alive)的循環(huán)函數(shù)去實(shí)現(xiàn)wait等待,alive是一直檢測(cè)線程的存活狀態(tài)乒裆,它相當(dāng)于套利,在那個(gè)線程執(zhí)行join推励,即在哪個(gè)線程執(zhí)行wait,調(diào)用的線程對(duì)象可以理解為lock對(duì)象,即調(diào)用了lock.wait(), sleep方法是一直持有鎖的狀態(tài)肉迫,同時(shí)sleep是靜態(tài)方法验辞,它通過(guò)currentThread獲取當(dāng)前線程的lock,并只能作用當(dāng)前線程
- 守護(hù)線程意思是后臺(tái)服務(wù)線程喊衫,比如垃圾回收線程跌造,要理解它就知道另一個(gè)用戶(hù)線程,用戶(hù)線程是維持程序運(yùn)行狀態(tài)族购,或者說(shuō)jvm存活的線程壳贪,如果用戶(hù)線程都跑完了,那么不管守護(hù)線程是否運(yùn)行寝杖,程序和jvm都會(huì)退出违施,當(dāng)然此時(shí),守護(hù)線程也會(huì)退出瑟幕,由此可以看出守護(hù)線程和用戶(hù)線程對(duì)于程序運(yùn)行的相關(guān)性磕蒲。由上述線程的init方法可以看出,子線程的創(chuàng)建會(huì)繼承一些默認(rèn)參數(shù)只盹,包含是否為守護(hù)線程辣往,它是低級(jí)別的線程,不依賴(lài)于終端殖卑,但是依賴(lài)于系統(tǒng)站削,與系統(tǒng)“同生共死”。