JDK源码解析之 java.lang.Thread
位于java.lang包下的Thread類(lèi)是非常重要的線程類(lèi),它實(shí)現(xiàn)了Runnable接口,今天我們來(lái)學(xué)習(xí)一下Thread類(lèi),在學(xué)習(xí)Thread類(lèi)之前,先介紹與線程相關(guān)知識(shí):線程的幾種狀態(tài)、上下文切換,然后接著介紹Thread類(lèi)中的方法的具體使用
一、線程的狀態(tài)
線程從創(chuàng)建到最終的消亡,要經(jīng)歷若干個(gè)狀態(tài)。一般來(lái)說(shuō),線程包括以下這幾個(gè)狀態(tài):
創(chuàng)建(new)、就緒(runnable)、運(yùn)行(running)、阻塞(blocked)、time waiting、waiting、消亡(dead)
當(dāng)需要新起一個(gè)線程來(lái)執(zhí)行某個(gè)子任務(wù)時(shí),就創(chuàng)建了一個(gè)線程。但是線程創(chuàng)建之后,不會(huì)立即進(jìn)入就緒狀態(tài),因?yàn)榫€程的運(yùn)行需要一些條件(比如內(nèi)存資源,譬如程序計(jì)數(shù)器、Java棧、本地方法棧都是線程私有的,所以需要為線程分配一定的內(nèi)存空間),只有線程運(yùn)行需要的所有條件滿(mǎn)足了,才進(jìn)入就緒狀態(tài)。
當(dāng)線程進(jìn)入就緒狀態(tài)后,不代表立刻就能獲取CPU執(zhí)行時(shí)間,也許此時(shí)CPU正在執(zhí)行其他的事情,因此它要等待。當(dāng)?shù)玫紺PU執(zhí)行時(shí)間之后,線程便真正進(jìn)入運(yùn)行狀態(tài)。
線程在運(yùn)行狀態(tài)過(guò)程中,可能有多個(gè)原因?qū)е庐?dāng)前線程不繼續(xù)運(yùn)行下去,比如用戶(hù)主動(dòng)讓線程睡眠(睡眠一定的時(shí)間之后再重新執(zhí)行)、用戶(hù)主動(dòng)讓線程等待,或者被同步塊給阻塞,此時(shí)就對(duì)應(yīng)著多個(gè)狀態(tài):time waiting(睡眠或等待一定的事件)、waiting(等待被喚醒)、blocked(阻塞)。
當(dāng)由于突然中斷或者子任務(wù)執(zhí)行完畢,線程就會(huì)被消亡。
下面這副圖描述了線程從創(chuàng)建到消亡之間的狀態(tài):
在有些教程上將blocked、waiting、time waiting統(tǒng)稱(chēng)為阻塞狀態(tài),這個(gè)也是可以的,只不過(guò)這里我想將線程的狀態(tài)和Java中的方法調(diào)用聯(lián)系起來(lái),所以將waiting和time waiting兩個(gè)狀態(tài)分離出來(lái)。
二、上下文切換
對(duì)于單核CPU來(lái)說(shuō)(對(duì)于多核CPU,此處就理解為一個(gè)核),CPU在一個(gè)時(shí)刻只能運(yùn)行一個(gè)線程,當(dāng)在運(yùn)行一個(gè)線程的過(guò)程中轉(zhuǎn)去運(yùn)行另外一個(gè)線程,這個(gè)叫做線程上下文切換(對(duì)于進(jìn)程也是類(lèi)似)。
由于可能當(dāng)前線程的任務(wù)并沒(méi)有執(zhí)行完畢,所以在切換時(shí)需要保存線程的運(yùn)行狀態(tài),以便下次重新切換回來(lái)時(shí)能夠繼續(xù)切換之前的狀態(tài)運(yùn)行。舉個(gè)簡(jiǎn)單的例子:比如一個(gè)線程A正在讀取一個(gè)文件的內(nèi)容,正讀到文件的一半,此時(shí)需要暫停線程A,轉(zhuǎn)去執(zhí)行線程B,當(dāng)再次切換回來(lái)執(zhí)行線程A的時(shí)候,我們不希望線程A又從文件的開(kāi)頭來(lái)讀取。
因此需要記錄線程A的運(yùn)行狀態(tài),那么會(huì)記錄哪些數(shù)據(jù)呢?因?yàn)橄麓位謴?fù)時(shí)需要知道在這之前當(dāng)前線程已經(jīng)執(zhí)行到哪條指令了,所以需要記錄程序計(jì)數(shù)器的值,另外比如說(shuō)線程正在進(jìn)行某個(gè)計(jì)算的時(shí)候被掛起了,那么下次繼續(xù)執(zhí)行的時(shí)候需要知道之前掛起時(shí)變量的值時(shí)多少,因此需要記錄CPU寄存器的狀態(tài)。所以一般來(lái)說(shuō),線程上下文切換過(guò)程中會(huì)記錄程序計(jì)數(shù)器、CPU寄存器狀態(tài)等數(shù)據(jù)。
說(shuō)簡(jiǎn)單點(diǎn)的:對(duì)于線程的上下文切換實(shí)際上就是 存儲(chǔ)和恢復(fù)CPU狀態(tài)的過(guò)程,它使得線程執(zhí)行能夠從中斷點(diǎn)恢復(fù)執(zhí)行。
雖然多線程可以使得任務(wù)執(zhí)行的效率得到提升,但是由于在線程切換時(shí)同樣會(huì)帶來(lái)一定的開(kāi)銷(xiāo)代價(jià),并且多個(gè)線程會(huì)導(dǎo)致系統(tǒng)資源占用的增加,所以在進(jìn)行多線程編程時(shí)要注意這些因素。
三、類(lèi)定義
class Thread implements Runnable {}Thread實(shí)現(xiàn)了Runnable接口,Runnable接口是線程輔助類(lèi),僅定義了一個(gè)方法run()方法,用于實(shí)現(xiàn)多線程
四、成員變量
//線程的名字 private volatile String name; //線程的優(yōu)先級(jí) private int priority;private Thread threadQ; private long eetop;/* Whether or not to single_step this thread. */ private boolean single_step;//是否守護(hù)進(jìn)程 private boolean daemon = false;/* JVM state */ private boolean stillborn = false;//將要執(zhí)行的任務(wù) private Runnable target;//線程組表示一個(gè)線程的集合。此外,線程組也可以包含其他線程組。線程組構(gòu)成一棵樹(shù),在樹(shù)中,除了初始線程組外,每個(gè)線程組都有一個(gè)父線程組。 private ThreadGroup group;/* The context ClassLoader for this thread */ private ClassLoader contextClassLoader;/* The inherited AccessControlContext of this thread */ private AccessControlContext inheritedAccessControlContext;//第幾個(gè)線程,在init初始化線程的時(shí)候用來(lái)賦給thread.name private static int threadInitNumber;ThreadLocal.ThreadLocalMap threadLocals = null;/** InheritableThreadLocal values pertaining to this thread. This map is* maintained by the InheritableThreadLocal class.*/ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;/** The requested stack size for this thread, or 0 if the creator did* not specify a stack size. It is up to the VM to do whatever it* likes with this number; some VMs will ignore it.*/private long stackSize;/** JVM-private state that persists after native thread termination.*/private long nativeParkEventPointer;// Thread ID private long tid;// 用來(lái)生成Thread ID使用 private static long threadSeqNumber;//線程從創(chuàng)建到最終的消亡,要經(jīng)歷若干個(gè)狀態(tài)。 // 一般來(lái)說(shuō),線程包括以下這幾個(gè)狀態(tài):創(chuàng)建(new)、就緒(runnable)、運(yùn)行(running)、阻塞(blocked)、time waiting、waiting、消亡(dead) private volatile int threadStatus = 0;五、常用方法
關(guān)系到線程運(yùn)行狀態(tài)的幾個(gè)方法:
1.start方法
start()用來(lái)啟動(dòng)一個(gè)線程,當(dāng)調(diào)用start方法后,系統(tǒng)才會(huì)開(kāi)啟一個(gè)新的線程來(lái)執(zhí)行用戶(hù)定義的子任務(wù),在這個(gè)過(guò)程中,會(huì)為相應(yīng)的線程分配需要的資源。
public synchronized void start() {if (threadStatus != 0)throw new IllegalThreadStateException();group.add(this);boolean started = false;try {start0();started = true;} finally {try {if (!started) {group.threadStartFailed(this);}} catch (Throwable ignore) {/* do nothing. If start0 threw a Throwable thenit will be passed up the call stack */}} }2.run方法
run()方法是不需要用戶(hù)來(lái)調(diào)用的,當(dāng)通過(guò)start方法啟動(dòng)一個(gè)線程之后,當(dāng)線程獲得了CPU執(zhí)行時(shí)間,便進(jìn)入run方法體去執(zhí)行具體的任務(wù)。注意,繼承Thread類(lèi)必須重寫(xiě)run方法,在run方法中定義具體要執(zhí)行的任務(wù)。
@Override public void run() {if (target != null) {target.run();} }3.sleep方法
sleep相當(dāng)于讓線程睡眠,交出CPU,讓CPU去執(zhí)行其他的任務(wù)。
如果需要讓當(dāng)前正在執(zhí)行的線程暫停一段時(shí)間,并進(jìn)入阻塞狀態(tài),則可以通過(guò)調(diào)用Thread類(lèi)的靜態(tài)sleep()方法來(lái)實(shí)現(xiàn)。
當(dāng)當(dāng)前線程調(diào)用sleep()方法進(jìn)入阻塞狀態(tài)后,在其睡眠時(shí)間內(nèi),該線程不會(huì)獲得執(zhí)行機(jī)會(huì),即使系統(tǒng)中沒(méi)有其他可執(zhí)行線程,處于sleep()中的線程也不會(huì)執(zhí)行,因此sleep()方法常用來(lái)暫停程序的執(zhí)行
但是有一點(diǎn)要非常注意,sleep方法不會(huì)釋放鎖,也就是說(shuō)如果當(dāng)前線程持有對(duì)某個(gè)對(duì)象的鎖,則即使調(diào)用sleep方法,其他線程也無(wú)法訪問(wèn)這個(gè)對(duì)象。
sleep方法有兩個(gè)重載版本:
public static native void sleep(long millis) throws InterruptedException; //參數(shù)為毫秒 public static void sleep(long millis, int nanos)//第一參數(shù)為毫秒,第二個(gè)參數(shù)為納 throws InterruptedException {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++;}sleep(millis); }4.yield方法
為本地方法,也就是說(shuō) yield() 是由 C 或 C++ 實(shí)現(xiàn)的,yield()方法和sleep()方法有點(diǎn)相似,它也是Thread類(lèi)提供的一個(gè)靜態(tài)方法,它也可以讓當(dāng)前正在執(zhí)行的線程暫停,但它不會(huì)阻塞該線程,它只是將該線程轉(zhuǎn)入到就緒狀態(tài)。即讓當(dāng)前線程暫停一下,讓系統(tǒng)的線程調(diào)度器重新調(diào)度一次,完全可能的情況是:當(dāng)某個(gè)線程調(diào)用了yield()方法暫停之后,線程調(diào)度器又將其調(diào)度出來(lái)重新執(zhí)行。
調(diào)用yield方法會(huì)讓當(dāng)前線程交出CPU權(quán)限,讓CPU去執(zhí)行其他的線程。它跟sleep方法類(lèi)似,同樣不會(huì)釋放鎖。但是yield不能控制具體的交出CPU的時(shí)間,另外,當(dāng)某個(gè)線程調(diào)用了yield()方法之后,只有優(yōu)先級(jí)與當(dāng)前線程相同或者比當(dāng)前線程更高的處于就緒狀態(tài)的線程才會(huì)獲得執(zhí)行機(jī)會(huì)。
注意,調(diào)用yield方法并不會(huì)讓線程進(jìn)入阻塞狀態(tài),而是讓線程重回就緒狀態(tài),它只需要等待重新獲取CPU執(zhí)行時(shí)間,這一點(diǎn)是和sleep方法不一樣的。
public static native void yield();5.join方法
join方法有三個(gè)重載版本:
public final synchronized void join(long millis) throws InterruptedException {long base = System.currentTimeMillis();long now = 0;if (millis < 0) {throw new IllegalArgumentException("timeout value is negative");}if (millis == 0) {while (isAlive()) {wait(0);}} else {while (isAlive()) {long delay = millis - now;if (delay <= 0) {break;}wait(delay);now = System.currentTimeMillis() - base;}} }參數(shù)為毫秒
public final synchronized void join(long millis, int nanos) throws InterruptedException {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); }第一參數(shù)為毫秒,第二個(gè)參數(shù)為納秒
public final void join() throws InterruptedException {join(0); }假如在main線程中,調(diào)用thread.join方法,則main方法會(huì)等待thread線程執(zhí)行完畢或者等待一定的時(shí)間。如果調(diào)用的是無(wú)參join方法,則等待thread執(zhí)行完畢,如果調(diào)用的是指定了時(shí)間參數(shù)的join方法,則等待一定的事件。
6.interrupt方法
interrupt,顧名思義,即中斷的意思。單獨(dú)調(diào)用interrupt方法可以使得處于阻塞狀態(tài)的線程拋出一個(gè)異常,也就說(shuō),它可以用來(lái)中斷一個(gè)正處于阻塞狀態(tài)的線程;另外,通過(guò)interrupt方法和isInterrupted()方法來(lái)停止正在運(yùn)行的線程。
public void interrupt() {if (this != Thread.currentThread())checkAccess();synchronized (blockerLock) {Interruptible b = blocker;if (b != null) {interrupt0(); // Just to set the interrupt flagb.interrupt(this);return;}}interrupt0(); }7.interrupted方法
interrupted()函數(shù)是Thread靜態(tài)方法,用來(lái)檢測(cè)當(dāng)前線程的interrupt狀態(tài),檢測(cè)完成后,狀態(tài)清空。通過(guò)下面的interrupted源碼我們能夠知道,此方法首先調(diào)用isInterrupted方法,而isInterrupted方法是一個(gè)重載的native方法private native boolean isInterrupted(boolean ClearInterrupted) 通過(guò)方法的注釋能夠知道,用來(lái)測(cè)試線程是否已經(jīng)中斷,參數(shù)用來(lái)決定是否重置中斷標(biāo)志。
public static boolean interrupted() {return currentThread().isInterrupted(true); }public boolean isInterrupted() {return isInterrupted(false); }private native boolean isInterrupted(boolean ClearInterrupted);關(guān)系到線程屬性的幾個(gè)方法:
8.getId
用來(lái)得到線程ID
9.getName和setName
用來(lái)得到或者設(shè)置線程名稱(chēng)。
10.getPriority和setPriority
用來(lái)獲取和設(shè)置線程優(yōu)先級(jí)。
11.setDaemon和isDaemon
用來(lái)設(shè)置線程是否成為守護(hù)線程和判斷線程是否是守護(hù)線程。
守護(hù)線程和用戶(hù)線程的區(qū)別在于:守護(hù)線程依賴(lài)于創(chuàng)建它的線程,而用戶(hù)線程則不依賴(lài)。舉個(gè)簡(jiǎn)單的例子:如果在main線程中創(chuàng)建了一個(gè)守護(hù)線程,當(dāng)main方法運(yùn)行完畢之后,守護(hù)線程也會(huì)隨著消亡。而用戶(hù)線程則不會(huì),用戶(hù)線程會(huì)一直運(yùn)行直到其運(yùn)行完畢。在JVM中,像垃圾收集器線程就是守護(hù)線程。
Thread類(lèi)有一個(gè)比較常用的靜態(tài)方法currentThread()用來(lái)獲取當(dāng)前線程。
六、總結(jié)
在上面已經(jīng)說(shuō)到了Thread類(lèi)中的大部分方法,那么Thread類(lèi)中的方法調(diào)用到底會(huì)引起線程狀態(tài)發(fā)生怎樣的變化呢?下面一幅圖就是在上面的圖上進(jìn)行改進(jìn)而來(lái)的:
總結(jié)
以上是生活随笔為你收集整理的JDK源码解析之 java.lang.Thread的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Redis的AOF日志
- 下一篇: etcd命令