本文部分摘自《Java 并發(fā)編程的藝術(shù)》
線程簡介
1. 什么是線程?
現(xiàn)代操作系統(tǒng)在運(yùn)行一個(gè)程序時(shí),會(huì)為其創(chuàng)建一個(gè)進(jìn)程,一個(gè)進(jìn)程里可以創(chuàng)建多個(gè)線程?,F(xiàn)代操作系統(tǒng)調(diào)度的最小單元是線程,也叫輕量級(jí)進(jìn)程。這些線程都擁有各自的計(jì)數(shù)器、堆棧和局部變量等屬性,并且能訪問共享的內(nèi)存變量。處理器在這些線程上高速切換,讓使用者覺得這些線程在同時(shí)執(zhí)行
2. 為什么使用多線程?
使用多線程的原因主要有以下幾點(diǎn):
-
更多的處理器核心
通過使用多線程技術(shù),將計(jì)算邏輯分配到多個(gè)處理器核心上,可以顯著減少程序的處理時(shí)間
-
更快的響應(yīng)時(shí)間
有時(shí)我們會(huì)編寫一些較為復(fù)雜的代碼(主要指業(yè)務(wù)邏輯),可以使用多線程技術(shù),將數(shù)據(jù)一致性不強(qiáng)的操作派發(fā)給其他線程處理(也可以使用消息隊(duì)列)。這樣做的好處是響應(yīng)用戶請求的線程能夠盡可能快地處理完成,縮短了響應(yīng)時(shí)間
-
更好的編程模型
Java 已經(jīng)為多線程編程提供了一套良好的編程模型,開發(fā)人員只需根據(jù)問題需要建立合適的模型即可
線程優(yōu)先級(jí)
現(xiàn)代操作系統(tǒng)基本采用時(shí)分的形式調(diào)度運(yùn)行的線程,操作系統(tǒng)會(huì)分出一個(gè)個(gè)時(shí)間片,線程分配到若干時(shí)間片,當(dāng)線程的時(shí)間片用完了發(fā)生線程調(diào)度,并等待下次分配。線程分配到的時(shí)間片多少也就決定了線程使用處理器資源的多少,而線程優(yōu)先級(jí)就是決定線程需要多或少分配一些處理器資源的線程屬性
在 Java 線程中,通過一個(gè)整型成員變量 priority 來控制優(yōu)先級(jí),優(yōu)先級(jí)的范圍從 1 ~ 10,在線程構(gòu)建時(shí)可以通過 setPriority(int) 方法來修改優(yōu)先級(jí),默認(rèn)優(yōu)先級(jí)是 5,優(yōu)先級(jí)高的線程分配時(shí)間片的數(shù)量要多于優(yōu)先級(jí)低的線程。不過,在不同的 JVM 以及操作系統(tǒng)上,線程規(guī)劃會(huì)存在差異,有些操作系統(tǒng)甚至?xí)雎跃€程優(yōu)先級(jí)的設(shè)定
public class Priority {
private static volatile boolean notStart = true;
private static volatile boolean notEnd = true;
public static void main(String[] args) throws Exception {
List<Job> jobs = new ArrayList<Job>();
for (int i = 0; i < 10; i++) {
int priority = i < 5 ? Thread.MIN_PRIORITY : Thread.MAX_PRIORITY;
Job job = new Job(priority);
jobs.add(job);
Thread thread = new Thread(job, "Thread:" + i);
thread.setPriority(priority);
thread.start();
}
notStart = false;
TimeUnit.SECONDS.sleep(10);
notEnd = false;
for (Job job : jobs) {
System.out.println("Job Priority : " + job.priority + ", Count : " + job.jobCount);
}
}
static class Job implements Runnable {
private int priority;
private long jobCount;
public Job(int priority) {
this.priority = priority;
}
@Override
public void run() {
while (notStart) {
Thread.yield();
}
while (notEnd) {
Thread.yield();
jobCount++;
}
}
}
}
運(yùn)行該示例,在筆者機(jī)器上對(duì)應(yīng)的輸出如下

筆者使用的環(huán)境為:Win10 + JDK11,從輸出可以看到線程優(yōu)先級(jí)起作用了
線程的狀態(tài)
Java 線程在運(yùn)行的生命周期中可能處于下表所示的六種不同的狀態(tài),在給定的一個(gè)時(shí)刻,線程只能處于其中的一個(gè)狀態(tài)
狀態(tài)名稱 |
說明 |
NEW |
初始狀態(tài),線程被構(gòu)建,但還沒調(diào)用 start() 方法 |
RUNNABLE |
運(yùn)行狀態(tài),Java 線程將操作系統(tǒng)中的就緒和運(yùn)行兩種狀態(tài)籠統(tǒng)地稱作“運(yùn)行中” |
BLOCKED |
阻塞狀態(tài),表示線程阻塞于鎖 |
WAITING |
等待狀態(tài),表示線程進(jìn)入等待狀態(tài),進(jìn)入該狀態(tài)表示當(dāng)前線程需要等待其他線程做出一些特定動(dòng)作(通知或中斷) |
TIME_WAITING |
超時(shí)等待狀態(tài),該狀態(tài)不同于 WAITING,它是可以在指定的時(shí)間自行返回的 |
TERMINATED |
終止?fàn)顟B(tài),表示當(dāng)前線程已經(jīng)執(zhí)行完畢 |
線程在自身的生命周期中,并不是固定地處于某一狀態(tài),而是隨著代碼的執(zhí)行在不同的狀態(tài)之間進(jìn)行切換

Daemon 線程
Daemon 線程是一種支持型線程,主要被用作程序中后臺(tái)調(diào)度以及支持性工作。這意味著,當(dāng)一個(gè) Java 虛擬機(jī)中不存在 Daemon 線程的時(shí)候,Java 虛擬機(jī)將退出??梢哉{(diào)用 Thread.setDaemon(true) 將線程設(shè)置為 Daemon 線程
使用 Daemon 線程需要注意兩點(diǎn):
- Daemon 屬性需要在啟動(dòng)線程之前設(shè)置,不能在啟動(dòng)線程之后設(shè)置
- 在構(gòu)建 Daemon 線程時(shí),不能依靠 finally 塊中的內(nèi)容來確保執(zhí)行或關(guān)閉清理資源的邏輯。因?yàn)樵?Java 虛擬機(jī)退出時(shí) Daemon 線程中的 finally 塊并不一定會(huì)執(zhí)行
啟動(dòng)和終止線程
1. 構(gòu)造線程
在運(yùn)行線程之前首先要構(gòu)造一個(gè)線程對(duì)象,線程對(duì)象在構(gòu)造的時(shí)候需提供線程需的屬性,如線程所屬的線程組、是否是 Daemon 線程等信息
2. 啟動(dòng)線程
線程對(duì)象在初始化完成之后,調(diào)用 start() 方法即可啟動(dòng)線程
3. 理解中斷
中斷可以理解為線程的一個(gè)標(biāo)識(shí)位屬性,標(biāo)識(shí)一個(gè)運(yùn)行中的線程是否被其他線程進(jìn)行了中斷操作。中斷好比其他線程對(duì)該線程打了個(gè)招呼,其他線程可以通過調(diào)用該線程的 interrupt() 方法對(duì)其進(jìn)行中斷操作
線程通過檢查自身是否被中斷進(jìn)行響應(yīng),線程通過 isInterrupted() 來進(jìn)行判斷是否被中斷,也可以調(diào)用靜態(tài)方法 Tread.interrupted() 對(duì)當(dāng)前線程的中斷標(biāo)識(shí)位進(jìn)行復(fù)位。如果線程已經(jīng)處于終結(jié)狀態(tài),即時(shí)線程被中斷過,在調(diào)用該對(duì)象的 isInterrupted() 時(shí)依舊會(huì)返回 false
許多聲明拋出 InterruptedException 的方法在拋出異常之前,Java 虛擬機(jī)會(huì)先將該線程的中斷標(biāo)識(shí)位清除,然后拋出 InterruptedException,此時(shí)調(diào)用 isInterrupted() 方法將會(huì)返回 false
在下面的例子中,首先創(chuàng)建兩個(gè)線程 SleepThread 和 BusyThread,前者不停地睡眠,后者一直運(yùn)行,分別對(duì)兩個(gè)線程分別進(jìn)行中斷操作,觀察中斷標(biāo)識(shí)位
public class Interrupted {
public static void main(String[] args) throws InterruptedException {
// sleepThread 不停的嘗試睡眠
Thread sleepThread = new Thread(new SleepRunner(), "SleepThread");
sleepThread.setDaemon(true);
// busyThread 不停的運(yùn)行
Thread busyThread = new Thread(new BusyRunner(), "BusyThread");
busyThread.setDaemon(true);
sleepThread.start();
busyThread.start();
// 休眠 5 秒,讓 sleepThread 和 busyThread 充分運(yùn)行
TimeUnit.SECONDS.sleep(5);
sleepThread.interrupt();
busyThread.interrupt();
System.out.println("SleepThread interrupted is " + sleepThread.isInterrupted());
System.out.println("BusyThread interrupted is " + busyThread.isInterrupted());
// 防止 sleepThread 和 busyThreaad 立刻退出
SleepUtils.second(2);
}
static class SleepRunner implements Runnable {
@Override
public void run() {
while (true) {
SleepUtils.second(10);
}
}
}
static class BusyRunner implements Runnable {
@Override
public void run() {
while (true) {
}
}
}
}
輸出如下

從結(jié)果可以看出,拋出 InterruptedException 的線程 SleepThread,其中斷標(biāo)識(shí)位被清除了,而一直忙碌運(yùn)行的線程 BusyThread 的中斷標(biāo)識(shí)位沒有被清除
4. 安全地終止線程
前面提到的中斷操作是一種簡便的線程間交互方式,適合用來取消或停止任務(wù)。除了中斷以外,還可以利用一個(gè) boolean 變量來控制是否需要停止任務(wù)并終止線程
下面的示例中,創(chuàng)建了一個(gè)線程 CountThread,它不斷地進(jìn)行變量累加,而主線程嘗試對(duì)其進(jìn)行中斷操作和停止操作
public class Shutdown {
public static void main(String[] args) throws InterruptedException {
Runner one = new Runner();
Thread countThread = new Thread(one, "CountThread");
countThread.start();
// 睡眠一秒,main 線程對(duì) CountThread 進(jìn)行中斷,使 CountThread 能夠感知中斷而結(jié)束
TimeUnit.SECONDS.sleep(1);
countThread.interrupt();
Runner two = new Runner();
countThread = new Thread(two, "CountThread");
countThread.start();
// 睡眠一秒,main 線程對(duì) Runner two 進(jìn)行中斷,使 CountThread 能夠感知 on 為 false 而結(jié)束
TimeUnit.SECONDS.sleep(1);
two.cancel();
}
private static class Runner implements Runnable {
private long i;
private volatile boolean on = true;
@Override
public void run() {
while (on && !Thread.currentThread().isInterrupted()) {
i++;
}
System.out.println("Count i = " + i);
}
public void cancel() {
on = false;
}
}
}
main 線程通過中斷操作和 cancel() 方法均可使 CountThread 得以終止。這種通過標(biāo)識(shí)位或者中斷操作的方式能夠使線程在終止時(shí)有機(jī)會(huì)去清理資源,而不是武斷地將線程停止,更加安全和優(yōu)雅
|