?? 本文以及示例源碼已歸檔在 javacore
一内贮、線程簡介
什么是進程
簡言之,進程可視為一個正在運行的程序汞斧。它是系統(tǒng)運行程序的基本單位夜郁,因此進程是動態(tài)的啤它。進程是具有一定獨立功能的程序關(guān)于某個數(shù)據(jù)集合上的一次運行活動谚咬。進程是操作系統(tǒng)進行資源分配的基本單位。
什么是線程
線程是操作系統(tǒng)進行調(diào)度的基本單位锻霎。線程也叫輕量級進程(Light Weight Process)庙睡,在一個進程里可以創(chuàng)建多個線程事富,這些線程都擁有各自的計數(shù)器、堆棧和局部變量等屬性乘陪,并且能夠訪問共享的內(nèi)存變量统台。
進程和線程的區(qū)別
- 一個程序至少有一個進程,一個進程至少有一個線程啡邑。
- 線程比進程劃分更細贱勃,所以執(zhí)行開銷更小,并發(fā)性更高谣拣。
- 進程是一個實體募寨,擁有獨立的資源;而同一個進程中的多個線程共享進程的資源森缠。
二拔鹰、線程基本用法
線程(
Thread
)基本方法清單:
方法 描述 run
線程的執(zhí)行實體。 start
線程的啟動方法贵涵。 currentThread
返回對當前正在執(zhí)行的線程對象的引用列肢。 setName
設(shè)置線程名稱。 getName
獲取線程名稱宾茂。 setPriority
設(shè)置線程優(yōu)先級瓷马。Java 中的線程優(yōu)先級的范圍是 [1,10],一般來說跨晴,高優(yōu)先級的線程在運行時會具有優(yōu)先權(quán)欧聘。可以通過 thread.setPriority(Thread.MAX_PRIORITY)
的方式設(shè)置端盆,默認優(yōu)先級為 5怀骤。getPriority
獲取線程優(yōu)先級费封。 setDaemon
設(shè)置線程為守護線程。 isDaemon
判斷線程是否為守護線程蒋伦。 isAlive
判斷線程是否啟動弓摘。 interrupt
中斷另一個線程的運行狀態(tài)。 interrupted
測試當前線程是否已被中斷痕届。通過此方法可以清除線程的中斷狀態(tài)韧献。換句話說,如果要連續(xù)調(diào)用此方法兩次研叫,則第二次調(diào)用將返回 false(除非當前線程在第一次調(diào)用清除其中斷狀態(tài)之后且在第二次調(diào)用檢查其狀態(tài)之前再次中斷)锤窑。 join
可以使一個線程強制運行,線程強制運行期間蓝撇,其他線程無法運行果复,必須等待此線程完成之后才可以繼續(xù)執(zhí)行陈莽。 Thread.sleep
靜態(tài)方法渤昌。將當前正在執(zhí)行的線程休眠。 Thread.yield
靜態(tài)方法走搁。將當前正在執(zhí)行的線程暫停独柑,讓其他線程執(zhí)行。
創(chuàng)建線程
創(chuàng)建線程有三種方式:
- 繼承
Thread
類 - 實現(xiàn)
Runnable
接口 - 實現(xiàn)
Callable
接口
繼承 Thread 類
通過繼承 Thread
類創(chuàng)建線程的步驟:
- 定義
Thread
類的子類私植,并覆寫該類的run
方法忌栅。run
方法的方法體就代表了線程要完成的任務(wù),因此把run
方法稱為執(zhí)行體曲稼。 - 創(chuàng)建
Thread
子類的實例索绪,即創(chuàng)建了線程對象。 - 調(diào)用線程對象的
start
方法來啟動該線程贫悄。
public class ThreadDemo {
public static void main(String[] args) {
// 實例化對象
MyThread tA = new MyThread("Thread 線程-A");
MyThread tB = new MyThread("Thread 線程-B");
// 調(diào)用線程主體
tA.start();
tB.start();
}
static class MyThread extends Thread {
private int ticket = 5;
MyThread(String name) {
super(name);
}
@Override
public void run() {
while (ticket > 0) {
System.out.println(Thread.currentThread().getName() + " 賣出了第 " + ticket + " 張票");
ticket--;
}
}
}
}
實現(xiàn) Runnable 接口
實現(xiàn) Runnable
接口優(yōu)于繼承 Thread
類瑞驱,因為:
- Java 不支持多重繼承,所有的類都只允許繼承一個父類窄坦,但可以實現(xiàn)多個接口唤反。如果繼承了
Thread
類就無法繼承其它類,這不利于擴展鸭津。 - 類可能只要求可執(zhí)行就行彤侍,繼承整個
Thread
類開銷過大。
通過實現(xiàn) Runnable
接口創(chuàng)建線程的步驟:
- 定義
Runnable
接口的實現(xiàn)類逆趋,并覆寫該接口的run
方法盏阶。該run
方法的方法體同樣是該線程的線程執(zhí)行體。 - 創(chuàng)建
Runnable
實現(xiàn)類的實例闻书,并以此實例作為Thread
的 target 來創(chuàng)建Thread
對象名斟,該Thread
對象才是真正的線程對象吴汪。 - 調(diào)用線程對象的
start
方法來啟動該線程。
public class RunnableDemo {
public static void main(String[] args) {
// 實例化對象
Thread tA = new Thread(new MyThread(), "Runnable 線程-A");
Thread tB = new Thread(new MyThread(), "Runnable 線程-B");
// 調(diào)用線程主體
tA.start();
tB.start();
}
static class MyThread implements Runnable {
private int ticket = 5;
@Override
public void run() {
while (ticket > 0) {
System.out.println(Thread.currentThread().getName() + " 賣出了第 " + ticket + " 張票");
ticket--;
}
}
}
}
實現(xiàn) Callable 接口
繼承 Thread 類 和 實現(xiàn) Callable 接口這兩種創(chuàng)建線程的方式都沒有返回值蒸眠。所以漾橙,線程執(zhí)行完后,無法得到執(zhí)行結(jié)果楞卡。但如果期望得到執(zhí)行結(jié)果該怎么做霜运?
為了解決這個問題,Java 1.5 后蒋腮,提供了
Callable
接口和Future
接口淘捡,通過它們,可以在線程執(zhí)行結(jié)束后池摧,返回執(zhí)行結(jié)果焦除。
通過實現(xiàn) Callable
接口創(chuàng)建線程的步驟:
- 創(chuàng)建
Callable
接口的實現(xiàn)類,并實現(xiàn)call
方法作彤。該call
方法將作為線程執(zhí)行體膘魄,并且有返回值。 - 創(chuàng)建
Callable
實現(xiàn)類的實例竭讳,使用FutureTask
類來包裝Callable
對象创葡,該FutureTask
對象封裝了該Callable
對象的call
方法的返回值。 - 使用
FutureTask
對象作為Thread
對象的 target 創(chuàng)建并啟動新線程绢慢。 - 調(diào)用
FutureTask
對象的get
方法來獲得線程執(zhí)行結(jié)束后的返回值灿渴。
public class CallableDemo {
public static void main(String[] args) {
Callable<Long> callable = new MyThread();
FutureTask<Long> future = new FutureTask<>(callable);
new Thread(future, "Callable 線程").start();
try {
System.out.println("任務(wù)耗時:" + (future.get() / 1000000) + "毫秒");
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
static class MyThread implements Callable<Long> {
private int ticket = 10000;
@Override
public Long call() {
long begin = System.nanoTime();
while (ticket > 0) {
System.out.println(Thread.currentThread().getName() + " 賣出了第 " + ticket + " 張票");
ticket--;
}
long end = System.nanoTime();
return (end - begin);
}
}
}
FAQ
start
和 run
方法有什么區(qū)別
-
run
方法是線程的執(zhí)行體。 -
start
方法會啟動線程胰舆,然后 JVM 會讓這個線程去執(zhí)行run
方法骚露。
可以直接調(diào)用 Thread
類的 run
方法么
- 可以。但是如果直接調(diào)用
Thread
的run
方法缚窿,它的行為就會和普通的方法一樣棘幸。 - 為了在新的線程中執(zhí)行我們的代碼,必須使用
Thread
的start
方法滨攻。
線程休眠
使用 Thread.sleep
方法可以使得當前正在執(zhí)行的線程進入休眠狀態(tài)够话。
使用 Thread.sleep
需要向其傳入一個整數(shù)值,這個值表示線程將要休眠的毫秒數(shù)光绕。
Thread.sleep
方法可能會拋出 InterruptedException
女嘲,因為異常不能跨線程傳播回 main
中,因此必須在本地進行處理诞帐。線程中拋出的其它異常也同樣需要在本地進行處理欣尼。
public class ThreadSleepDemo {
public static void main(String[] args) {
new Thread(new MyThread("線程A", 500)).start();
new Thread(new MyThread("線程B", 1000)).start();
new Thread(new MyThread("線程C", 1500)).start();
}
static class MyThread implements Runnable {
/** 線程名稱 */
private String name;
/** 休眠時間 */
private int time;
private MyThread(String name, int time) {
this.name = name;
this.time = time;
}
@Override
public void run() {
try {
// 休眠指定的時間
Thread.sleep(this.time);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(this.name + "休眠" + this.time + "毫秒。");
}
}
}
線程禮讓
Thread.yield
方法的調(diào)用聲明了當前線程已經(jīng)完成了生命周期中最重要的部分,可以切換給其它線程來執(zhí)行 愕鼓。
該方法只是對線程調(diào)度器的一個建議钙态,而且也只是建議具有相同優(yōu)先級的其它線程可以運行。
public class ThreadYieldDemo {
public static void main(String[] args) {
MyThread t = new MyThread();
new Thread(t, "線程A").start();
new Thread(t, "線程B").start();
}
static class MyThread implements Runnable {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "運行菇晃,i = " + i);
if (i == 2) {
System.out.print("線程禮讓:");
Thread.yield();
}
}
}
}
}
終止線程
Thread
中的stop
方法有缺陷册倒,已廢棄。使用
Thread.stop
停止線程會導(dǎo)致它解鎖所有已鎖定的監(jiān)視器(由于未經(jīng)檢查的ThreadDeath
異常會在堆棧中傳播磺送,這是自然的結(jié)果)驻子。 如果先前由這些監(jiān)視器保護的任何對象處于不一致狀態(tài),則損壞的對象將對其他線程可見估灿,從而可能導(dǎo)致任意行為崇呵。Thread.stop
的許多用法應(yīng)由僅修改某些變量以指示目標線程應(yīng)停止運行的代碼代替。 目標線程應(yīng)定期檢查此變量馅袁,如果該變量指示要停止運行域慷,則應(yīng)按有序方式從其運行方法返回。如果目標線程等待很長時間(例如汗销,在條件變量上)犹褒,則應(yīng)使用中斷方法來中斷等待。
當一個線程運行時大溜,另一個線程可以直接通過 interrupt
方法中斷其運行狀態(tài)化漆。
public class ThreadInterruptDemo {
public static void main(String[] args) {
MyThread mt = new MyThread(); // 實例化Runnable子類對象
Thread t = new Thread(mt, "線程"); // 實例化Thread對象
t.start(); // 啟動線程
try {
Thread.sleep(2000); // 線程休眠2秒
} catch (InterruptedException e) {
System.out.println("3、休眠被終止");
}
t.interrupt(); // 中斷線程執(zhí)行
}
static class MyThread implements Runnable {
@Override
public void run() {
System.out.println("1钦奋、進入run()方法");
try {
Thread.sleep(10000); // 線程休眠10秒
System.out.println("2、已經(jīng)完成了休眠");
} catch (InterruptedException e) {
System.out.println("3疙赠、休眠被終止");
return; // 返回調(diào)用處
}
System.out.println("4付材、run()方法正常結(jié)束");
}
}
}
如果一個線程的 run
方法執(zhí)行一個無限循環(huán),并且沒有執(zhí)行 sleep
等會拋出 InterruptedException
的操作圃阳,那么調(diào)用線程的 interrupt
方法就無法使線程提前結(jié)束厌衔。
但是調(diào)用 interrupt
方法會設(shè)置線程的中斷標記,此時調(diào)用 interrupted
方法會返回 true
捍岳。因此可以在循環(huán)體中使用 interrupted
方法來判斷線程是否處于中斷狀態(tài)富寿,從而提前結(jié)束線程。
安全地終止線程有兩種方法:
- 定義
volatile
標志位锣夹,在run
方法中使用標志位控制線程終止 - 使用
interrupt
方法和Thread.interrupted
方法配合使用來控制線程終止
示例:使用 volatile
標志位控制線程終止
public class ThreadStopDemo2 {
public static void main(String[] args) throws Exception {
MyTask task = new MyTask();
Thread thread = new Thread(task, "MyTask");
thread.start();
TimeUnit.MILLISECONDS.sleep(50);
task.cancel();
}
private static class MyTask implements Runnable {
private volatile boolean flag = true;
private volatile long count = 0L;
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " 線程啟動");
while (flag) {
System.out.println(count++);
}
System.out.println(Thread.currentThread().getName() + " 線程終止");
}
/**
* 通過 volatile 標志位來控制線程終止
*/
public void cancel() {
flag = false;
}
}
}
示例:使用 interrupt
方法和 Thread.interrupted
方法配合使用來控制線程終止
public class ThreadStopDemo3 {
public static void main(String[] args) throws Exception {
MyTask task = new MyTask();
Thread thread = new Thread(task, "MyTask");
thread.start();
TimeUnit.MILLISECONDS.sleep(50);
thread.interrupt();
}
private static class MyTask implements Runnable {
private volatile long count = 0L;
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " 線程啟動");
// 通過 Thread.interrupted 和 interrupt 配合來控制線程終止
while (!Thread.interrupted()) {
System.out.println(count++);
}
System.out.println(Thread.currentThread().getName() + " 線程終止");
}
}
}
守護線程
什么是守護線程页徐?
- 守護線程(Daemon Thread)是在后臺執(zhí)行并且不會阻止 JVM 終止的線程。當所有非守護線程結(jié)束時银萍,程序也就終止变勇,同時會殺死所有守護線程。
- 與守護線程(Daemon Thread)相反的贴唇,叫用戶線程(User Thread)搀绣,也就是非守護線程飞袋。
為什么需要守護線程?
- 守護線程的優(yōu)先級比較低链患,用于為系統(tǒng)中的其它對象和線程提供服務(wù)巧鸭。典型的應(yīng)用就是垃圾回收器。
如何使用守護線程麻捻?
- 可以使用
isDaemon
方法判斷線程是否為守護線程蹄皱。 - 可以使用
setDaemon
方法設(shè)置線程為守護線程。- 正在運行的用戶線程無法設(shè)置為守護線程芯肤,所以
setDaemon
必須在thread.start
方法之前設(shè)置巷折,否則會拋出llegalThreadStateException
異常; - 一個守護線程創(chuàng)建的子線程依然是守護線程崖咨。
- 不要認為所有的應(yīng)用都可以分配給守護線程來進行服務(wù)锻拘,比如讀寫操作或者計算邏輯。
- 正在運行的用戶線程無法設(shè)置為守護線程芯肤,所以
public class ThreadDaemonDemo {
public static void main(String[] args) {
Thread t = new Thread(new MyThread(), "線程");
t.setDaemon(true); // 此線程在后臺運行
System.out.println("線程 t 是否是守護進程:" + t.isDaemon());
t.start(); // 啟動線程
}
static class MyThread implements Runnable {
@Override
public void run() {
while (true) {
System.out.println(Thread.currentThread().getName() + "在運行击蹲。");
}
}
}
}
參考閱讀:Java 中守護線程的總結(jié)
FAQ
sleep署拟、yield、join 方法有什么區(qū)別
-
yield
方法-
yield
方法會 讓線程從Running
狀態(tài)轉(zhuǎn)入Runnable
狀態(tài)歌豺。 - 當調(diào)用了
yield
方法后推穷,只有與當前線程相同或更高優(yōu)先級的Runnable
狀態(tài)線程才會獲得執(zhí)行的機會。
-
-
sleep
方法-
sleep
方法會 讓線程從Running
狀態(tài)轉(zhuǎn)入Waiting
狀態(tài)类咧。 -
sleep
方法需要指定等待的時間馒铃,超過等待時間后,JVM 會將線程從Waiting
狀態(tài)轉(zhuǎn)入Runnable
狀態(tài)痕惋。 - 當調(diào)用了
sleep
方法后区宇,無論什么優(yōu)先級的線程都可以得到執(zhí)行機會。 -
sleep
方法不會釋放“鎖標志”值戳,也就是說如果有synchronized
同步塊议谷,其他線程仍然不能訪問共享數(shù)據(jù)。
-
-
join
-
join
方法會 讓線程從Running
狀態(tài)轉(zhuǎn)入Waiting
狀態(tài)堕虹。 - 當調(diào)用了
join
方法后卧晓,當前線程必須等待調(diào)用join
方法的線程結(jié)束后才能繼續(xù)執(zhí)行。
-
為什么 sleep 和 yield 方法是靜態(tài)的
Thread
類的 sleep
和 yield
方法將處理 Running
狀態(tài)的線程赴捞。
所以在其他處于非 Running
狀態(tài)的線程上執(zhí)行這兩個方法是沒有意義的逼裆。這就是為什么這些方法是靜態(tài)的。它們可以在當前正在執(zhí)行的線程中工作螟炫,并避免程序員錯誤的認為可以在其他非運行線程調(diào)用這些方法波附。
Java 線程是否按照線程優(yōu)先級嚴格執(zhí)行
即使設(shè)置了線程的優(yōu)先級,也無法保證高優(yōu)先級的線程一定先執(zhí)行。
原因在于線程優(yōu)先級依賴于操作系統(tǒng)的支持掸屡,然而封寞,不同的操作系統(tǒng)支持的線程優(yōu)先級并不相同,不能很好的和 Java 中線程優(yōu)先級一一對應(yīng)仅财。
三狈究、線程間通信
當多個線程可以一起工作去解決某個問題時,如果某些部分必須在其它部分之前完成盏求,那么就需要對線程進行協(xié)調(diào)抖锥。
wait/notify/notifyAll
-
wait
-wait
方法使得線程釋放其占有的對象鎖,讓線程從Running
狀態(tài)轉(zhuǎn)入Waiting
狀態(tài)碎罚,并等待notify
/notifyAll
來喚醒 磅废。如果沒有釋放鎖,那么其它線程就無法進入對象的同步方法或者同步控制塊中荆烈,那么就無法執(zhí)行notify
或者notifyAll
來喚醒掛起的線程拯勉,造成死鎖。 -
notify
- 喚醒一個正在Waiting
狀態(tài)的線程憔购,并讓它拿到對象鎖宫峦,具體喚醒哪一個線程由 JVM 控制 。 -
notifyAll
- 喚醒所有正在Waiting
狀態(tài)的線程玫鸟,接下來它們需要競爭對象鎖导绷。
注意:
wait
、notify
屎飘、notifyAll
都是Object
類中的方法妥曲,而非Thread
。wait
枚碗、notify
逾一、notifyAll
只能用在synchronized
方法或者synchronized
代碼塊中使用,否則會在運行時拋出IllegalMonitorStateException
肮雨。為什么
wait
、notify
箱玷、notifyAll
不定義在Thread
中怨规?為什么wait
、notify
锡足、notifyAll
要配合synchronized
使用波丰?首先,需要了解幾個基本知識點:
- 每一個 Java 對象都有一個與之對應(yīng)的 監(jiān)視器(monitor)
- 每一個監(jiān)視器里面都有一個 對象鎖 舶得、一個 等待隊列掰烟、一個 同步隊列
了解了以上概念,我們回過頭來理解前面兩個問題。
為什么這幾個方法不定義在
Thread
中纫骑?由于每個對象都擁有對象鎖蝎亚,讓當前線程等待某個對象鎖,自然應(yīng)該基于這個對象(
Object
)來操作先馆,而非使用當前線程(Thread
)來操作发框。因為當前線程可能會等待多個線程的鎖,如果基于線程(Thread
)來操作煤墙,就非常復(fù)雜了梅惯。為什么
wait
、notify
仿野、notifyAll
要配合synchronized
使用铣减?如果調(diào)用某個對象的
wait
方法,當前線程必須擁有這個對象的對象鎖脚作,因此調(diào)用wait
方法必須在synchronized
方法和synchronized
代碼塊中葫哗。
生產(chǎn)者、消費者模式是 wait
鳖枕、notify
魄梯、notifyAll
的一個經(jīng)典使用案例:
public class ThreadWaitNotifyDemo02 {
private static final int QUEUE_SIZE = 10;
private static final PriorityQueue<Integer> queue = new PriorityQueue<>(QUEUE_SIZE);
public static void main(String[] args) {
new Producer("生產(chǎn)者A").start();
new Producer("生產(chǎn)者B").start();
new Consumer("消費者A").start();
new Consumer("消費者B").start();
}
static class Consumer extends Thread {
Consumer(String name) {
super(name);
}
@Override
public void run() {
while (true) {
synchronized (queue) {
while (queue.size() == 0) {
try {
System.out.println("隊列空,等待數(shù)據(jù)");
queue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
queue.notifyAll();
}
}
queue.poll(); // 每次移走隊首元素
queue.notifyAll();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 從隊列取走一個元素宾符,隊列當前有:" + queue.size() + "個元素");
}
}
}
}
static class Producer extends Thread {
Producer(String name) {
super(name);
}
@Override
public void run() {
while (true) {
synchronized (queue) {
while (queue.size() == QUEUE_SIZE) {
try {
System.out.println("隊列滿酿秸,等待有空余空間");
queue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
queue.notifyAll();
}
}
queue.offer(1); // 每次插入一個元素
queue.notifyAll();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 向隊列取中插入一個元素,隊列當前有:" + queue.size() + "個元素");
}
}
}
}
}
join
在線程操作中魏烫,可以使用 join
方法讓一個線程強制運行辣苏,線程強制運行期間,其他線程無法運行哄褒,必須等待此線程完成之后才可以繼續(xù)執(zhí)行稀蟋。
public class ThreadJoinDemo {
public static void main(String[] args) {
MyThread mt = new MyThread(); // 實例化Runnable子類對象
Thread t = new Thread(mt, "mythread"); // 實例化Thread對象
t.start(); // 啟動線程
for (int i = 0; i < 50; i++) {
if (i > 10) {
try {
t.join(); // 線程強制運行
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("Main 線程運行 --> " + i);
}
}
static class MyThread implements Runnable {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println(Thread.currentThread().getName() + " 運行,i = " + i); // 取得當前線程的名字
}
}
}
}
管道
管道輸入/輸出流和普通的文件輸入/輸出流或者網(wǎng)絡(luò)輸入/輸出流不同之處在于呐赡,它主要用于線程之間的數(shù)據(jù)傳輸退客,而傳輸?shù)拿浇闉閮?nèi)存。
管道輸入/輸出流主要包括了如下 4 種具體實現(xiàn):PipedOutputStream
链嘀、PipedInputStream
萌狂、PipedReader
和 PipedWriter
,前兩種面向字節(jié)怀泊,而后兩種面向字符茫藏。
public class Piped {
public static void main(String[] args) throws Exception {
PipedWriter out = new PipedWriter();
PipedReader in = new PipedReader();
// 將輸出流和輸入流進行連接,否則在使用時會拋出IOException
out.connect(in);
Thread printThread = new Thread(new Print(in), "PrintThread");
printThread.start();
int receive = 0;
try {
while ((receive = System.in.read()) != -1) {
out.write(receive);
}
} finally {
out.close();
}
}
static class Print implements Runnable {
private PipedReader in;
Print(PipedReader in) {
this.in = in;
}
public void run() {
int receive = 0;
try {
while ((receive = in.read()) != -1) {
System.out.print((char) receive);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
四霹琼、線程狀態(tài)
java.lang.Thread.State
中定義了 6 種不同的線程狀態(tài)务傲,在給定的一個時刻凉当,線程只能處于其中的一個狀態(tài)。
以下是各狀態(tài)的說明售葡,以及狀態(tài)間的聯(lián)系:
新建(New) - 尚未調(diào)用
start
方法的線程處于此狀態(tài)看杭。此狀態(tài)意味著:創(chuàng)建的線程尚未啟動。可運行(Runnable) - 已經(jīng)調(diào)用了
start
方法的線程處于此狀態(tài)天通。此狀態(tài)意味著:線程已經(jīng)在 JVM 中運行泊窘。但是在操作系統(tǒng)層面,它可能處于運行狀態(tài)像寒,也可能等待資源調(diào)度(例如處理器資源)烘豹,資源調(diào)度完成就進入運行狀態(tài)。所以該狀態(tài)的可運行是指可以被運行诺祸,具體有沒有運行要看底層操作系統(tǒng)的資源調(diào)度携悯。阻塞(Blocked) - 請求獲取 monitor lock 從而進入
synchronized
函數(shù)或者代碼塊,但是其它線程已經(jīng)占用了該 monitor lock筷笨,所以處于阻塞狀態(tài)憔鬼。要結(jié)束該狀態(tài)進入Runnable
,從而需要其他線程釋放 monitor lock胃夏。此狀態(tài)意味著:線程處于被阻塞狀態(tài)轴或。-
等待(Waiting) - 此狀態(tài)意味著:線程等待被其他線程顯式地喚醒。 阻塞和等待的區(qū)別在于仰禀,阻塞是被動的照雁,它是在等待獲取 monitor lock。而等待是主動的答恶,通過調(diào)用
Object.wait
等方法進入饺蚊。進入方法 退出方法 沒有設(shè)置 Timeout 參數(shù)的 Object.wait
方法Object.notify
/Object.notifyAll
沒有設(shè)置 Timeout 參數(shù)的 Thread.join
方法被調(diào)用的線程執(zhí)行完畢 LockSupport.park
方法LockSupport.unpark
-
定時等待(Timed waiting) - 此狀態(tài)意味著:無需等待其它線程顯式地喚醒,在一定時間之后會被系統(tǒng)自動喚醒悬嗓。
進入方法 退出方法 Thread.sleep
方法時間結(jié)束 設(shè)置了 Timeout 參數(shù)的 Object.wait
方法時間結(jié)束 / Object.notify
/Object.notifyAll
設(shè)置了 Timeout 參數(shù)的 Thread.join
方法時間結(jié)束 / 被調(diào)用的線程執(zhí)行完畢 LockSupport.parkNanos
方法LockSupport.unpark
LockSupport.parkUntil
方法LockSupport.unpark
終止(Terminated) - 線程
run
方法執(zhí)行結(jié)束污呼,或者因異常退出了run
方法。此狀態(tài)意味著:線程結(jié)束了生命周期包竹。
參考資料
- 《Java 并發(fā)編程實戰(zhàn)》
- 《Java 并發(fā)編程的藝術(shù)》
- 進程和線程關(guān)系及區(qū)別
- Java 線程中 yield 與 join 方法的區(qū)別
- sleep()燕酷,wait(),yield()和 join()方法的區(qū)別
- Java 并發(fā)編程:線程間協(xié)作的兩種方式:wait周瞎、notify悟狱、notifyAll 和 Condition
- Java 并發(fā)編程:Callable、Future 和 FutureTask
- StackOverflow VisualVM - Thread States
- Java 中守護線程的總結(jié)
- Java 并發(fā)
- Why must wait() always be in synchronized block