一、線程中的概念
- 進(jìn)程:程序運(yùn)行資源分配的最小單位,進(jìn)程內(nèi)部有多個(gè)線程,會(huì)共享這個(gè)進(jìn)程的資源;
- 線程:CPU 調(diào)度的最小單位,在一個(gè) Java 程序進(jìn)程中,最少有 2 個(gè)線程,Main 線程 與 GC 守護(hù)線程;
- 上下文切換:CPU 為每個(gè)線程分配一個(gè)時(shí)間段,它稱為時(shí)間片,如果在時(shí)間片結(jié)束時(shí)線程還在運(yùn)行,則暫停這個(gè)線程的運(yùn)行,并且 CPU 分配給另一個(gè)線程;
- 并行: 同一時(shí)刻,可以處理事情的能力,真正意義上的多個(gè)任務(wù)同時(shí)執(zhí)行;
- 并發(fā):在單位時(shí)間內(nèi),可以處理事情的能力,多個(gè)任務(wù)交替執(zhí)行;
- 線程模型:
- 用戶級(jí)線程(ULT):用戶程序?qū)崿F(xiàn),不依賴操作系統(tǒng)核心,應(yīng)用提供創(chuàng)建、同步、調(diào)度和管理線程的函數(shù)來控制用戶線程。不需要用戶態(tài)/核心態(tài)切換,速度快。內(nèi)核對(duì) ULT 無感知,線程阻塞則進(jìn)程阻塞;
- 內(nèi)核級(jí)線程(KLT):系統(tǒng)內(nèi)核管理線程,內(nèi)核保存線程的狀態(tài)和上下文信息,線程阻塞不會(huì)引起進(jìn)程阻塞。在多處理器系統(tǒng)上,多線程在多處理器上并行運(yùn)行。線程的創(chuàng)建、調(diào)度和管理由內(nèi)核完成,效率比 ULT 要慢,進(jìn)程操作要快;
Java 線程創(chuàng)建是依賴于系統(tǒng)內(nèi)核(KLT),通過 JVM 調(diào)用系統(tǒng)庫創(chuàng)建內(nèi)核線程,內(nèi)核線程與 Java 線程是 1:1 的映射關(guān)系。
二、Thread 類解析
2.1 線程的狀態(tài)

線程狀態(tài)定義位于 Thread.State 枚舉類中,定義了如下 6 種狀態(tài)
- NEW(新建狀態(tài)):實(shí)現(xiàn) Runnable 或繼承 Thread 使用 new 關(guān)鍵字實(shí)例對(duì)象,但沒有調(diào)用 start() 方法;
- RUNNABLE(運(yùn)行狀態(tài)):由 READY(就緒狀態(tài))和 RUNNING (運(yùn)行中)組成;
- READY(就緒狀態(tài)):當(dāng)線程調(diào)用 start() 方法,程序進(jìn)入 READY(就緒狀態(tài))等待線程調(diào)度器選中執(zhí)行,還能通過以下方法進(jìn)入 READY(就緒狀態(tài));
- Thread.yield() 當(dāng)前線程暫時(shí)放棄 CPU 搶占權(quán);
- Thread.sleep()、Object.join()、Object.wait() 當(dāng)前線程掛起,掛起的線程進(jìn)入等待隊(duì)列,等待時(shí)間結(jié)束或被喚醒;
- 當(dāng)前線程獲取到鎖;
- RUNNING(運(yùn)行中):當(dāng)前線程被線程調(diào)度器執(zhí)行,這也是線程進(jìn)入該狀態(tài)的唯一方法;
- READY(就緒狀態(tài)):當(dāng)線程調(diào)用 start() 方法,程序進(jìn)入 READY(就緒狀態(tài))等待線程調(diào)度器選中執(zhí)行,還能通過以下方法進(jìn)入 READY(就緒狀態(tài));
- TIMED_WAITING(超時(shí)等待):線程處于一定時(shí)間后自動(dòng)被喚醒;
- WAITING(等待狀態(tài)):線程等待被喚醒;
- BLOCKED(阻塞狀態(tài)):當(dāng)前線程進(jìn)入同步代碼塊,未獲得鎖對(duì)象的線程,進(jìn)入該鎖對(duì)象的同步隊(duì)列,等待搶鎖;
- TERMINATED(終止?fàn)顟B(tài)):當(dāng)前線程執(zhí)行完成;
2.2 優(yōu)先級(jí)與守護(hù)線程
優(yōu)先級(jí)代表了線程被線程調(diào)度器選中執(zhí)行的機(jī)會(huì)大小,優(yōu)先級(jí)高的可能先執(zhí)行,優(yōu)先級(jí)低的可能后執(zhí)行。在 Java 中,線程優(yōu)先級(jí)被分為 1 到 10, 默認(rèn)優(yōu)先級(jí)為 5。在 Thread 類中定義如下,Java 中對(duì)線程所設(shè)置的優(yōu)先級(jí)只是給操作系統(tǒng)的一個(gè)建議,而真正調(diào)用順序,是由操作系統(tǒng)的線程調(diào)度算法完成決定的。
/**
* 線程最小優(yōu)先級(jí)
*/
public static final int MIN_PRIORITY = 1;
/**
* 線程默認(rèn)優(yōu)先級(jí)
*/
public static final int NORM_PRIORITY = 5;
/**
* 線程最大優(yōu)先級(jí)
*/
public static final int MAX_PRIORITY = 10;
守護(hù)線程
在創(chuàng)建線程時(shí),默認(rèn)都是非守護(hù)線程,要?jiǎng)?chuàng)建守護(hù)線程需要將 daemon 屬性設(shè)置為 true。守護(hù)線程的優(yōu)先級(jí)很低,JVM 在退出時(shí),不會(huì)關(guān)注有無守護(hù)線程,JVM 仍然會(huì)退出,一般守護(hù)線程用于監(jiān)控的工作。當(dāng)線程已經(jīng)啟動(dòng)時(shí),如果調(diào)用 setDaemon 方法將其設(shè)置為守護(hù)線程,將會(huì)拋出 IllegalThreadStateException 異常。在守護(hù)線程中創(chuàng)建的線程仍然是守護(hù)線程。
2.3 構(gòu)建一個(gè)線程
Thread 類提供了多種構(gòu)造方法
public Thread() {
this(null, null, "Thread-" + nextThreadNum(), 0);
}
public Thread(Runnable target) {
this(null, target, "Thread-" + nextThreadNum(), 0);
}
/**
* 創(chuàng)建一個(gè)繼承給定 AccessControlContext 的新線程,但不繼承線程局部變量
*/
Thread(Runnable target, AccessControlContext acc) {
this(null, target, "Thread-" + nextThreadNum(), 0, acc, false);
}
public Thread(ThreadGroup group, Runnable target) {
this(group, target, "Thread-" + nextThreadNum(), 0);
}
public Thread(String name) {
this(null, null, name, 0);
}
public Thread(ThreadGroup group, String name) {
this(group, null, name, 0);
}
public Thread(Runnable target, String name) {
this(null, target, name, 0);
}
public Thread(ThreadGroup group, Runnable target, String name) {
this(group, target, name, 0);
}
public Thread(ThreadGroup group, Runnable target, String name, long stackSize) {
this(group, target, name, stackSize, null, true);
}
public Thread(ThreadGroup group, Runnable target, String name, long stackSize, boolean inheritThreadLocals) {
this(group, target, name, stackSize, null, inheritThreadLocals);
}
以上構(gòu)造方法底層均調(diào)用一個(gè)初始化方法,在 JDK 1.8 中為 init 方法,在 JDK 11 中為私有的 Thread 構(gòu)造方法,兩個(gè) JDK 版本的實(shí)現(xiàn)大致相似,以下源碼以 JDK 11 為準(zhǔn)
/**
* 初始化一個(gè)線程
* ThreadGroup g 線程組,線程組可以對(duì)組內(nèi)的線程進(jìn)行批量操作,比如批量打斷 interrupt
* Runnable target 要運(yùn)行的對(duì)象
* String name 線程名稱,默認(rèn)使用 "Thread-" + nextThreadNum(),nextThreadNum 方法返回自增的數(shù)字
* long stackSize 設(shè)置線程堆棧的大小,如果為 0 表示忽略次參數(shù)
* AccessControlContext acc 要繼承的 AccessControlContext,如果為 null,則為 AccessController.getContext()
* boolean inheritThreadLocals 是否繼承 ThreadLocal 中的初始值
*/
private Thread(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals) {
// 設(shè)置線程的名稱
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name;
// 獲取當(dāng)前線程作為父線程
Thread parent = currentThread();
/**
* 獲取系統(tǒng)安全管理器,安全管理器是一個(gè)允許應(yīng)用程序?qū)崿F(xiàn)安全策略的類
* 它允許應(yīng)用程序在執(zhí)行可能不安全或敏感操作之前確認(rèn)該操作是什么以及是否允許執(zhí)行該操作的安全上下文中嘗試該操作
*/
SecurityManager security = System.getSecurityManager();
// 設(shè)置線程組
if (g == null) {
if (security != null) {
g = security.getThreadGroup();
}
if (g == null) {
g = parent.getThreadGroup();
}
}
// 判斷當(dāng)前線程是否有權(quán)限修改該線程組
g.checkAccess();
//
if (security != null) {
if (isCCLOverridden(getClass())) {
security.checkPermission(SecurityConstants.SUBCLASS_IMPLEMENTATION_PERMISSION);
}
}
// 增加線程組中未啟動(dòng)線程的計(jì)數(shù)
g.addUnstarted();
// 子線程繼承線程組
this.group = g;
// 子線程繼承父線程的守護(hù)屬性
this.daemon = parent.isDaemon();
// 子線程繼承父線程的優(yōu)先級(jí)屬性
this.priority = parent.getPriority();
// 繼承父線程的 ClassLoader
if (security == null || isCCLOverridden(parent.getClass()))
this.contextClassLoader = parent.getContextClassLoader();
else
this.contextClassLoader = parent.contextClassLoader;
// 是否繼承的 AccessControlContext
this.inheritedAccessControlContext = acc != null ? acc : AccessController.getContext();
// 設(shè)置該線程的執(zhí)行對(duì)象
this.target = target;
// 更改該線程的優(yōu)先級(jí)
setPriority(priority);
// 當(dāng) inheritThreadLocals 為 true 或者 父線程的 inheritableThreadLocals 不為空時(shí),會(huì)將父線程中所有的 inheritableThreadLocals 傳遞給子線程
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
// 指定線程棧大小
this.stackSize = stackSize;
// 設(shè)置線程ID
this.tid = nextThreadID();
}
StackSize
StackSize 參數(shù)是指定 JVM 為該線程分配線程棧大小,其參數(shù)的效果高度依賴于平臺(tái)。JVM 可以自由的將 StackSize 視為建議,如果指定值過低,JVM 可能會(huì)更改為使用某些特定與平臺(tái)的最小值;如果指定的值過高,JVM 可能會(huì)改為使用某個(gè)特定于平臺(tái)的最大值。
2.4 啟動(dòng)一個(gè)線程
實(shí)例化一個(gè) Thread 類后,需要調(diào)用 start 方法才是真正開啟了一個(gè)線程,如果只是調(diào)用了其 run 方法,只是對(duì)其進(jìn)行了一個(gè)方法調(diào)用。由于線程的狀態(tài)是不可逆的,因此一個(gè)線程調(diào)用 start 方法后,該線程的狀態(tài)將會(huì)變?yōu)?RUNNABLE,如果多次調(diào)用 start 方法,該線程將拋出 IllegalThreadStateException 異常。
/**
* 開始執(zhí)行一個(gè)線程
*/
public synchronized void start() {
// 判斷線程狀態(tài)如果不為 NEW ,則拋出異常
if (threadStatus != 0) throw new IllegalThreadStateException();
// 通知線程組該線程即將開啟,并將線程組中未啟動(dòng)線程計(jì)數(shù)遞減
group.add(this);
// 線程啟動(dòng)標(biāo)識(shí)
boolean started = false;
try {
// 調(diào)用本地方法,真正開啟線程執(zhí)行,并將線程啟動(dòng)標(biāo)識(shí)設(shè)置為 ture
start0();
started = true;
} finally {
try {
// 如果發(fā)生異常,通知線程組該線程嘗試啟動(dòng)失敗,將線程組的狀態(tài)進(jìn)行回滾
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
/**
* Java 本地方法
*/
private native void start0();
2.5 線程中常用的方法
- wait 方法,調(diào)用該方法的線程進(jìn)入
WAITING狀態(tài),如果指定了超時(shí)時(shí)間,該線程進(jìn)入TIMED_WAITING狀態(tài),并且進(jìn)入等待此對(duì)象的等待鎖定池,,只有等待其他線程通知喚醒或中斷才會(huì)轉(zhuǎn)為RUNNABLE狀態(tài),需要注意 調(diào)用 wait() 方法后,會(huì)釋放對(duì)象的鎖,因此,wait 方法一般用在同步方法或同步代碼塊中; - sleep 方法,調(diào)用該方法的線程進(jìn)入
TIMED_WAITING狀態(tài), sleep 方法不會(huì)釋放當(dāng)前占有的鎖; - join 方法,等待指定線程終止,join 方法的本質(zhì)上就是調(diào)用 wait 方法;
- yield 方法,調(diào)用該方法的線程將讓出 CPU 執(zhí)行時(shí)間片,與其他線程一起重新競(jìng)爭(zhēng) CPU 執(zhí)行時(shí)間片;
- interrupt 方法,中斷此線程,并不是真的中斷此線程,而是設(shè)置標(biāo)志位(中斷位)來通知線程,如果此線程調(diào)用了
wait、join或sleep,則該線程的中斷狀態(tài)將被清除并拋出InterruptedException,實(shí)際上只有聲明了會(huì)拋出 InterruptedException 的函數(shù)才會(huì)拋出異常,調(diào)用 interrupt 方法并不會(huì)拋出 InterruptedException;t.isInterrupted 和 Thread.interrupted 的區(qū)別在于,前者只用于讀取中斷狀態(tài),后者不僅讀取中斷狀態(tài),還會(huì)重置中斷標(biāo)志位; - stop 和 destory 方法都是用來強(qiáng)制關(guān)閉線程的,但是官方并不建議使用,因?yàn)閺?qiáng)制關(guān)閉線程,將導(dǎo)致線程中使用的資源,如:文件描述符、網(wǎng)絡(luò)資源等不能正常關(guān)閉。最好的方式應(yīng)當(dāng)?shù)却€程執(zhí)行完成,并完整的釋放資源,如果是一個(gè)不斷循環(huán)的線程,應(yīng)當(dāng)使用線程之間的通信,讓主線程通知其關(guān)閉;
三、ThreadGroup 類解析
線程組是線程的集合,其結(jié)構(gòu)類似于樹形結(jié)構(gòu),在樹中,除了初始線程組外,其他線程組都有一個(gè)父線程組。一個(gè)線程可以訪問當(dāng)前線程組信息,但是不能訪問其線程組的父級(jí)線程組信息或其他線程組信息。
3.1 構(gòu)建一個(gè)線程
線程組提供了兩種創(chuàng)建方式。
/**
* 創(chuàng)建指定名稱的線程組,其父級(jí)線程組為當(dāng)前線程的線程組
*/
public ThreadGroup(String name) {
this(Thread.currentThread().getThreadGroup(), name);
}
/**
* 創(chuàng)建指定 父級(jí)線程組 和 名稱 的線程組
*/
public ThreadGroup(ThreadGroup parent, String name) {
this(checkParentAccess(parent), parent, name);
}
以上構(gòu)造方法均調(diào)用線程組私有構(gòu)造方法,對(duì)于一個(gè)線程組,它也擁有自己的名字、優(yōu)先級(jí)、是否為守護(hù)線程組,并通過 ThreadGroup parent 記錄其父級(jí)線程組,ThreadGroup groups[] 記錄其線程組下的子線程組。
/**
* 創(chuàng)建指定 父級(jí)線程組 和 名稱 的線程組
*/
private ThreadGroup(Void unused, ThreadGroup parent, String name) {
// 線程組名稱
this.name = name;
// 線程組的最大優(yōu)先級(jí),表示組內(nèi)所有的線程都不允許超過這個(gè)數(shù)值
this.maxPriority = parent.maxPriority;
// 線程組是否為守護(hù)線程組
this.daemon = parent.daemon;
// 當(dāng)前線程組的父級(jí)線程組
this.parent = parent;
// 將當(dāng)前線程組加入到 父級(jí)線程組中
parent.add(this);
}
另外 ThreadGroup 內(nèi)部還有個(gè)私有的默認(rèn)構(gòu)造方法,用于創(chuàng)建系統(tǒng)級(jí)線程組。
/**
* 創(chuàng)建一個(gè)不在任何線程組中的空線程組,該方法用于創(chuàng)建系統(tǒng)線程組
*/
private ThreadGroup() {
this.name = "system";
this.maxPriority = Thread.MAX_PRIORITY;
this.parent = null;
}
3.2 ThreadGroup 常用的方法
- activeCount 方法,返回此線程組及其子組中活動(dòng)線程數(shù)的估計(jì)值,因?yàn)樵摲椒▋?nèi)部使用遍歷內(nèi)部數(shù)據(jù),線程的數(shù)量可能會(huì)動(dòng)態(tài)發(fā)生變化,因此該方法返回的是估計(jì)值;
- activeGroupCount 方法,返回此線程組及其子組中活動(dòng)組的數(shù)目的估計(jì)值;
- checkAccess 方法,判斷當(dāng)前運(yùn)行的線程是否有權(quán)限修改當(dāng)前線程組,如果不能修改則拋出
SecurityException異常; - destroy 方法,銷毀當(dāng)前線程組及其子組,調(diào)用該方法時(shí),需要確保當(dāng)前線程組為空,且當(dāng)前線程組中的線程全部完成。如果線程組不為空或線程組已被銷毀,將拋出
IllegalThreadStateException異常; - enumerate 方法,將此線程組及其子組中的每個(gè)活動(dòng)線程復(fù)制到指定的數(shù)組中,如果 recurse 為 ture ,則此方法遞歸枚舉此線程組的所有子組,并且還包括對(duì)這些子組中每個(gè)活動(dòng)線程的應(yīng)用;
- interrupt 方法,中斷此線程組及其子組中的所有線程;
- list 方法,將有關(guān)此線程組的信息打印到標(biāo)準(zhǔn)輸出;
- parentOf 方法,判斷當(dāng)前線程組是否為傳入線程組的祖先級(jí)線程組;