摘要:程序執(zhí)行時(shí),至少會有一個(gè)線程在運(yùn)行,這個(gè)運(yùn)行的線程被稱為主線程。程序的終止是指除守護(hù)線程以外的線程全部終止。多線程程序由多個(gè)線程組成的程序稱為多線程程序。線程休眠期間可以被中斷,中斷將會拋出異常。
線程
我們在閱讀程序時(shí),表面看來是在跟蹤程序的處理流程,實(shí)際上跟蹤的是線程的執(zhí)行。
單線程程序
在單線程程序中,在某個(gè)時(shí)間點(diǎn)執(zhí)行的處理只有一個(gè)。
Java 程序執(zhí)行時(shí),至少會有一個(gè)線程在運(yùn)行,這個(gè)運(yùn)行的線程被稱為主線程(Main Thread)。
Java 程序在主線程運(yùn)行的同時(shí),后臺線程也在運(yùn)行,例如:垃圾回收線程、GUI 相關(guān)線程等。
Java 程序的終止是指除守護(hù)線程(Daemon Thread)以外的線程全部終止。守護(hù)線程是執(zhí)行后臺作業(yè)的線程,例如垃圾回收線程。我們可以通過 setDaemon() 方法把線程設(shè)置為守護(hù)線程。
多線程程序
由多個(gè)線程組成的程序稱為多線程程序(Multithreaded Program)。多個(gè)線程運(yùn)行時(shí),各個(gè)線程的運(yùn)行軌跡將會交織在一起,同一時(shí)間點(diǎn)執(zhí)行的處理有多個(gè)。
多線程應(yīng)用場景:
GUI 應(yīng)用程序:存在專門執(zhí)行 GUI 操作的線程(UI Thread)
耗時(shí)任務(wù):文件與網(wǎng)絡(luò)的 I/O 處理
網(wǎng)絡(luò)服務(wù)器同時(shí)處理多個(gè)客戶端請求場景
P.S. 使用 java.nio 包中的類,有時(shí)即便不使用線程,也可以執(zhí)行兼具性能和可擴(kuò)展性的 I/O 處理。
并行(parallel)與并發(fā)(concurrent)的區(qū)別
程序運(yùn)行存在順序、并行與并發(fā)模式。
順序(sequential)用于表示多個(gè)操作依次處理。
并行用于表示多個(gè)操作同時(shí)處理,取決于 CPU 的個(gè)數(shù)。
并發(fā)用于表示將一個(gè)操作分割成多個(gè)部分并且允許無序處理。
并發(fā)相對于順序和并行來說比較抽象。單個(gè) CPU 并發(fā)處理即為順序執(zhí)行,多個(gè) CPU 并發(fā)處理可以并行執(zhí)行。
如果是單個(gè) CPU,即便多個(gè)線程同時(shí)運(yùn)行,并發(fā)處理也只能順序執(zhí)行,在線程之間不斷切換。
并發(fā)處理包括:并發(fā)處理的順序執(zhí)行、并發(fā)處理的并行執(zhí)行。
線程和進(jìn)程的區(qū)別
線程之間共享內(nèi)存
進(jìn)程和線程之間最大的區(qū)別就是內(nèi)存是否共享。
通常,每個(gè)進(jìn)程都擁有彼此獨(dú)立的內(nèi)存空間。一個(gè)進(jìn)程不可以擅自讀取、寫入其他進(jìn)程的內(nèi)存。正因?yàn)槊總€(gè)進(jìn)程內(nèi)存空間獨(dú)立,無需擔(dān)心被其他進(jìn)程破壞。
線程之間共享內(nèi)存,使得線程之間的通信實(shí)現(xiàn)起來更加自然、簡單。一個(gè)線程向?qū)嵗袑懭雰?nèi)容,其他線程就可以讀取該實(shí)例的內(nèi)容。當(dāng)有多個(gè)線程可以訪問同一個(gè)實(shí)例時(shí),需要正確執(zhí)行互斥處理。
線程的上下文切換快
進(jìn)程和線程之間的另一個(gè)區(qū)別就是上下文切換的繁重程度。
當(dāng)運(yùn)行中的進(jìn)程進(jìn)行切換時(shí),進(jìn)程要暫時(shí)保存自身的當(dāng)前狀態(tài)(上下文信息)。而接著開始運(yùn)行的進(jìn)程需要恢復(fù)之前保存的自身的上下文信息。
當(dāng)運(yùn)行中的線程進(jìn)行切換時(shí),與進(jìn)程一樣,也會進(jìn)行上下文切換。但由于線程管理的上下文信息比進(jìn)程少,所以一般來說,線程的上下文切換要比進(jìn)程快。
當(dāng)執(zhí)行緊密關(guān)聯(lián)的多項(xiàng)工作時(shí),通常線程比進(jìn)程更加適合。
多線程程序的優(yōu)點(diǎn)和成本
優(yōu)點(diǎn):
充分利用硬件資源如多核 CPU、I/O 設(shè)備、網(wǎng)絡(luò)設(shè)備并行工作。
提高 GUI 應(yīng)用程序響應(yīng)性,UI Thread 專注界面繪制、用戶交互,額外開啟線程執(zhí)行后臺任務(wù)。
網(wǎng)絡(luò)應(yīng)用程序簡化建模,每個(gè)客戶端請求使用多帶帶的線程進(jìn)行處理。
缺點(diǎn)(成本):
創(chuàng)建線程需要消耗系統(tǒng)資源和時(shí)間,準(zhǔn)備線程私有的程序計(jì)數(shù)器和棧。
線程調(diào)度和切換同樣需要成本,線程切換出去時(shí)需要保存上下文狀態(tài)信息,以便再次切換回來時(shí)能夠恢復(fù)之前的上下文狀態(tài)。
相對而言,若是存在耗時(shí)任務(wù)需要放入子線程中實(shí)際執(zhí)行,線程使用成本可以不計(jì)。
多線程編程的重要性
硬件條件滿足多線程并行執(zhí)行的條件之外,還需要程序邏輯能夠保證多線程正確地運(yùn)行,考慮到線程之間的互斥處理和同步處理。
Thread 類 線程的創(chuàng)建與啟動創(chuàng)建與啟動線程的兩種方法:
利用 Thread 類的子類實(shí)例化,創(chuàng)建并啟動線程。
利用 Runnable 接口的實(shí)現(xiàn)類實(shí)例化,創(chuàng)建并啟動線程。
線程的創(chuàng)建與啟動步驟——方法一:
聲明 Thread 的子類(extends Thread),并重寫 run() 方法。
創(chuàng)建該類的實(shí)例
調(diào)用該實(shí)例的 start() 方法啟動線程
Thread 實(shí)例和線程本身不是同一個(gè)東西,創(chuàng)建 Thread 實(shí)例,線程并未啟動,直到 start() 方法調(diào)用,同樣就算線程終止了,實(shí)例也不會消失。但是一個(gè) Thread 實(shí)例只能創(chuàng)建一個(gè)線程,一旦調(diào)用 start() 方法,不管線程是否正常/異常結(jié)束,都無法再次通過調(diào)用 start() 方法創(chuàng)建新的線程。并且重復(fù)調(diào)用 start() 方法會拋出 IllegalThreadStateException 異常。
Thread run( ) 方法 和 start() 方法:
run() 方法是可以重復(fù)調(diào)用的,但是不會啟動新的線程,于當(dāng)前線程中執(zhí)行。run() 方法放置于 Runnable 接口旨在封裝操作。
start() 方法主要執(zhí)行以下操作:啟動新的線程,并在其中調(diào)用 run() 方法。
線程的創(chuàng)建與啟動步驟——方法二:
聲明類并實(shí)現(xiàn) Runnable 接口(implements Runnable),要求必須實(shí)現(xiàn) run() 方法。
創(chuàng)建該類的實(shí)例
以該實(shí)例作為參數(shù)創(chuàng)建 Thread 類的實(shí)例 Thread(Runnable target)
調(diào)用 Thread 類的實(shí)例的 start() 方法啟動線程
不管是利用 Thread 類的子類實(shí)例化的方法(1),還是利用 Runnable 接口的實(shí)現(xiàn)類實(shí)例化的方法(2),啟動新線程的方法最終都是 Thread 類的 start() 方法。
Java 中存在單繼承限制,如果類已經(jīng)有一個(gè)父類,則不能再繼承 Thread 類,這時(shí)可以通過實(shí)現(xiàn) Runnable 接口來實(shí)現(xiàn)創(chuàng)建并啟動新線程。
Thread 類本身實(shí)現(xiàn)了 Runnable 接口,并將 run() 方法的重寫(override)交由子類來完成。
線程的屬性id 和 name
通過 Thread(String name) 構(gòu)造方法或 void setName(String name),給 Thread 設(shè)置一個(gè)友好的名字,可以方便調(diào)試。
優(yōu)先級
Java 語言中,線程的優(yōu)先級從1到10,默認(rèn)為5。但因程序?qū)嶋H運(yùn)行的操作系統(tǒng)不同,優(yōu)先級會被映射到操作系統(tǒng)中的取值,因此 Java 語言中的優(yōu)先級主要是一種建議,多線程編程時(shí)不要過于依賴優(yōu)先級。
線程的狀態(tài)Thread.State 枚舉類型(Enum)包括:
NEW
線程實(shí)例化后,尚未調(diào)用 start() 方法啟動。
RUNNABLE
可運(yùn)行狀態(tài),正在運(yùn)行或準(zhǔn)備運(yùn)行。
BLOCKED
阻塞狀態(tài),等待其他線程釋放實(shí)例的鎖。
WAITING
等待狀態(tài),無限等待其他線程執(zhí)行特定操作。
TIMED_WAITING
時(shí)限等待狀態(tài),等待其他線程執(zhí)行指定的有限時(shí)間的操作。
TERMINATED
線程運(yùn)行結(jié)束
Thread 類的靜態(tài)方法 currentThread() 返回當(dāng)前正在執(zhí)行的線程對象。
sleep() 方法Thread 類的靜態(tài)方法 sleep() 能夠暫停(休眠)當(dāng)前線程(執(zhí)行該語句的線程)運(yùn)行,放棄占用 CPU。線程休眠期間可以被中斷,中斷將會拋出 InterruptedException 異常。sleep() 方法的參數(shù)以毫秒作為單位,不過通常情況下,JVM 無法精確控制時(shí)間。
sleep() 方法調(diào)用需要放在 try catch 語句中,可能拋出 InterruptedException 異常。InterruptedException 異常能夠取消線程處理,可以使用 interrupt() 方法在中途喚醒休眠狀態(tài)的線程。
多線程示例程序中經(jīng)常使用 sleep() 方法模擬耗時(shí)任務(wù)處理過程。
yield() 方法Thread 類的靜態(tài)方法 yield() 能夠暫停當(dāng)前線程(執(zhí)行該語句的線程)運(yùn)行,讓出 CPU 給其他線程優(yōu)先執(zhí)行。如果沒有正在等待的線程,或是線程的優(yōu)先級不高,當(dāng)前線程可能繼續(xù)運(yùn)行,即 yield() 方法無法確保暫停當(dāng)前線程。yield() 方法類似 sleep() 方法,但是不能指定暫停時(shí)間。
join() 方法Thread 類的實(shí)例方法,持有 Thread 實(shí)例的線程,將會等待調(diào)用 join() 方法的 Thread 實(shí)例代表的線程結(jié)束。等待期間可以被中斷,中斷將會拋出 InterruptedException 異常。
示例程序:
public class HelloThread extends Thread { @Override public void run() { System.out.println("hello"); } } public class Main { public static void main(String[] args) throws InterruptedException { Thread thread = new HelloThread(); thread.start(); thread.join(); } }
main() 方法所在的主線程將會等待 HelloThread 子線程執(zhí)行 run() 方法結(jié)束后再執(zhí)行,退出程序。
并發(fā)編程特性原子性
可見性
有序性
原子性操作問題原子性概念來源于數(shù)據(jù)庫系統(tǒng),一個(gè)事務(wù)(Transaction)中的所有操作,要么全部完成,要么全部不完成,不會結(jié)束在中間某個(gè)環(huán)節(jié)。事務(wù)在執(zhí)行過程中發(fā)生錯(cuò)誤,會被恢復(fù)(Rollback)到事務(wù)開始前的狀態(tài),就像這個(gè)事務(wù)從來沒有執(zhí)行過一樣。
并發(fā)編程的原子性指對于共享變量的操作是不可分的,Java 基本類型除 long、double 外的賦值操作是原子操作。
非原子操作例如:
counter++;
讀取 counter 的當(dāng)前值
在當(dāng)前值基礎(chǔ)上加1
將新值重新賦值給 counter
Java 語言的解決方式:
使用 synchronized 關(guān)鍵字
使用 java.util.concurrent.atomic 包
內(nèi)存可見性問題計(jì)算機(jī)結(jié)構(gòu)中,CPU 負(fù)責(zé)執(zhí)行指令,內(nèi)存負(fù)責(zé)讀寫數(shù)據(jù)。CPU 執(zhí)行速度遠(yuǎn)超內(nèi)存讀寫速度,緩解兩者速度不一致引入了高速緩存。 預(yù)先拷貝內(nèi)存數(shù)據(jù)的副本到緩存中,便于 CPU 直接快速使用。
因此計(jì)算機(jī)中除內(nèi)存之外,數(shù)據(jù)還有可能保存在 CPU 寄存器和各級緩存當(dāng)中。這樣一來,當(dāng)訪問一個(gè)變量時(shí),可能優(yōu)先從緩存中獲取,而非內(nèi)存;當(dāng)修改一個(gè)變量時(shí),可能先將修改寫到緩存中,稍后才會同步更新到內(nèi)存中。
對于單線程程序來說沒有太大問題,但是多線程程序并行執(zhí)行時(shí),內(nèi)存中的數(shù)據(jù)將會不一致,最新修改可能尚未同步到內(nèi)存中。需要提供一種機(jī)制保證多線程對應(yīng)的多核 CPU 緩存中的共享變量的副本彼此一致——緩存一致性協(xié)議。
Java 語言的解決方式:
使用 volatile 關(guān)鍵字
使用 synchronized 關(guān)鍵字
如果只是解決內(nèi)存可見性問題,使用 synchronized 關(guān)鍵字成本較高,考慮使用 volatile 關(guān)鍵字更輕量級的方式。
指令重排序問題有序性:即程序執(zhí)行的順序嚴(yán)格按照代碼的先后順序執(zhí)行。
Java 允許編譯器和處理器為了提高效率對指令進(jìn)行重排序,重排序過程不會影響到單線程程序的執(zhí)行,卻會可能影響到多線程程序并發(fā)執(zhí)行時(shí)候的正確性。
volatile 關(guān)鍵字細(xì)節(jié)
Java 使用 volatile 關(guān)鍵字修飾變量,保證可見性、有序性。
保證變量的值一旦被修改后立即更新寫入內(nèi)存,同時(shí)默認(rèn)從內(nèi)存讀取變量的值。(可見性)
禁止指令重排序(有序性)
但是 volatile 關(guān)鍵字無法保證對變量操作是原子性的。
線程的互斥處理(synchronized 關(guān)鍵字細(xì)節(jié))每個(gè)線程擁有獨(dú)立的程序計(jì)數(shù)器(指令執(zhí)行行號)、棧(方法參數(shù)、局部變量等信息),多個(gè)線程共享堆(對象),這些區(qū)域?qū)?yīng) JVM 內(nèi)存模型。當(dāng)多個(gè)線程操作堆區(qū)的對象時(shí)候,可能出現(xiàn)多線程共享內(nèi)存的問題。
競態(tài)條件銀行取款問題
if(可用余額大于等于取款金額) {可用余額減去取款金額}
多個(gè)線程同時(shí)操作時(shí),余額確認(rèn)(可用余額大于等于取款金額)和取款(可用余額減去取款金額)兩個(gè)操作可能穿插執(zhí)行,無法保證線程之間執(zhí)行順序。
線程 A | 線程 B |
---|---|
可用余額(1000)大于等于取款金額(1000)?是的 | 切換執(zhí)行線程 B |
線程 A 處于等待狀態(tài) | 可用余額(1000)大于等于取款金額(1000)?是的 |
線程 A 處于等待狀態(tài) | 可用余額減去取款金額(1000-1000 = 0) |
切換執(zhí)行線程 A | 線程 B 結(jié)束 |
可用余額減去取款金額(0 - 1000 = -1000) | 線程 B 結(jié)束 |
當(dāng)有多個(gè)線程同時(shí)操作同一個(gè)對象時(shí),可能出現(xiàn)競態(tài)條件(race condition),無法預(yù)期最終執(zhí)行結(jié)果,與執(zhí)行操作的時(shí)序有關(guān),需要“交通管制”——線程的互斥處理。
Java 使用 synchronized 關(guān)鍵字執(zhí)行線程的互斥處理。synchronized 關(guān)鍵字可以修飾類的實(shí)例方法、靜態(tài)方法和代碼塊。
synchronized 關(guān)鍵字保護(hù)的是對象而非方法、代碼塊,使用鎖來執(zhí)行線程的互斥處理。
synchronized 修飾靜態(tài)方法和實(shí)例方法時(shí)保護(hù)的是不同的對象:
synchronized 修飾實(shí)例方法是使用該類的實(shí)例對象 this。
synchronized 修飾靜態(tài)方法是使用該類的類對象 class。
每個(gè)對象擁有一個(gè)獨(dú)立的鎖,同一對象內(nèi)的所有 synchronized 方法共用。
基于 synchronized 關(guān)鍵字保護(hù)的是對象原則,有如下推論:
一個(gè)實(shí)例中的 synchronized 方法每次只能由一個(gè)線程運(yùn)行,而非 synchronized 方法則可以同時(shí)由多線程運(yùn)行。
一個(gè)實(shí)例中的多個(gè) synchronized 方法同樣無法多線程運(yùn)行。
不同實(shí)例中的 synchronized 方法可以同時(shí)由多線程運(yùn)行。
synchronized 修飾的靜態(tài)方法(this 對象)和實(shí)例方法(class 對象)之間,可以同時(shí)被多線程執(zhí)行。
synchronized 方法具有可重入性,即獲取鎖后可以在一個(gè) synchronized 方法,調(diào)用其他需要同樣鎖的 synchronized 方法。
一般在保護(hù)實(shí)例變量時(shí),將所有訪問該變量的方法設(shè)置為 synchronized 同步方法。
如果只是想讓方法中的某一部分由一個(gè)線程運(yùn)行,而非整個(gè)方法,則可使用 synchronized 代碼塊,精確控制互斥處理的執(zhí)行范圍。
嘗試獲取對象鎖,如果獲取到鎖進(jìn)入2,未獲取到鎖則加入鎖的等待隊(duì)列進(jìn)入阻塞狀態(tài)等待被喚醒。
執(zhí)行 synchronized 方法
釋放對象鎖,如果等待隊(duì)列存在線程正在等待獲取鎖,將其喚醒,當(dāng)有多個(gè)線程處于等待隊(duì)列,無法明確喚醒某一個(gè),由多個(gè)線程競爭獲取。
死鎖死鎖是指兩個(gè)或兩個(gè)以上的進(jìn)程(線程)在執(zhí)行過程中,因爭奪資源而造成的一種互相等待的現(xiàn)象,若無外力作用,它們都將無法推進(jìn)下去。
死鎖產(chǎn)生的四個(gè)必要條件
互斥條件:指進(jìn)程對所分配到的資源進(jìn)行排它性使用,即在一段時(shí)間內(nèi)某資源只由一個(gè)進(jìn)程占用。如果此時(shí)還有其它進(jìn)程請求資源,則請求者只能等待,直至占有資源的進(jìn)程用畢釋放。
請求和保持條件:指進(jìn)程已經(jīng)保持至少一個(gè)資源,但又提出了新的資源請求,而該資源已被其它進(jìn)程占有,此時(shí)請求進(jìn)程阻塞,但又對自己已獲得的其它資源保持不放。
不剝奪條件:指進(jìn)程已獲得的資源,在未使用完之前,不能被剝奪,只能在使用完時(shí)由自己釋放。
循環(huán)等待條件:指在發(fā)生死鎖時(shí),必然存在一個(gè)進(jìn)程——資源的環(huán)形鏈,即進(jìn)程集合 {P0,P1,P2,···,Pn} 中的 P0 正在等待一個(gè) P1 占用的資源;P1 正在等待 P2 占用的資源,……,Pn 正在等待已被 P0 占用的資源。
產(chǎn)生死鎖必須同時(shí)滿足上述四個(gè)條件,只要其中任一條件不成立,死鎖可避免。
應(yīng)該盡量避免在持有一個(gè)鎖的同時(shí),申請另一個(gè)鎖。如果確實(shí)需要多個(gè)鎖,應(yīng)該按照相同的順序獲取鎖。
線程的協(xié)作(wait()、notify() 方法細(xì)節(jié))多線程之間除了在競爭中做互斥處理,還需要相互協(xié)作。協(xié)作的前提是清楚共享的條件變量。
wait()、notify()、notifyAll() 都是 Object 類的實(shí)例方法,而不是 Thread 類中的方法。這三個(gè)方法與其說是針對線程的操作,倒不如說是針對實(shí)例的條件等待隊(duì)列的操作。
操作 obj 條件等待隊(duì)列中的線程(喚醒、等待):
obj.wait() 將當(dāng)前線程放入 obj 的條件等待隊(duì)列。
obj.notify() 從 obj 的條件等待隊(duì)列喚醒一個(gè)線程。
obj.notifyAll() 喚醒 obj 條件等待隊(duì)列中的所有線程。
wait() 等待方法每個(gè)對象擁有一個(gè)鎖和鎖的等待隊(duì)列,另外還有一個(gè)表示條件的等待隊(duì)列,用于線程間的協(xié)作。調(diào)用 wait() 方法會將當(dāng)前線程放入條件隊(duì)列等待,等待條件需要等待時(shí)間或者依靠其他線程改變(notify()/notifyAll() )。等待期間同樣可以被中斷,中斷將會拋出 InterruptedException 異常。
Object 類的 wait() 方法和 Thread 類的 sleep() 方法在控制線程上主要區(qū)別在于對象鎖是否釋放,從方法所屬類可以看出 Object 類的 wait() 方法包含對象鎖管理機(jī)制。
wait() 實(shí)例方法用于線程間通信協(xié)作
sleep() 靜態(tài)方法用于暫停當(dāng)前線程
兩者均會放棄占用 CPU
將當(dāng)前線程放入條件隊(duì)列等待,釋放對象鎖。
當(dāng)前線程進(jìn)入 WAITING、TIMED_WAITING 狀態(tài)。
等待時(shí)間或者被其他線程喚醒(notify()/notifyAll() ),從條件隊(duì)列中移除等待線程。
喚醒的線程獲得對象鎖,進(jìn)入 RUNNABLE 狀態(tài),從 wait() 方法返回,重新執(zhí)行等待條件檢查。
喚醒的線程無法獲得對象鎖,進(jìn)入 BLOCKED 狀態(tài),加入對象鎖的等待隊(duì)列,繼續(xù)等待。
notify() 喚醒方法notify() 和 notifyAll() 方法的區(qū)別
notify() 方法會喚醒等待隊(duì)列中的一個(gè)線程。
notifyAll() 方法會喚醒等待隊(duì)列中所有線程。
通常使用 notifyAll() 方法,相比于 notify() 方法代碼更具健壯性,但是喚醒多個(gè)線程速度慢些。
注意:調(diào)用 notify() 方法之后,喚醒條件隊(duì)列中等待的線程,并將其移除隊(duì)列。被喚醒的線程并不會立即運(yùn)行,因?yàn)閳?zhí)行 notify() 方法的線程還持有著鎖,等待 notify() 方法所處的同步(synchronized)代碼塊執(zhí)行結(jié)束才釋放鎖。隨后等待的線程獲得鎖從 wait() 方法返回,重新執(zhí)行等待條件檢查。
總結(jié):
線程必須持有實(shí)例的鎖才能執(zhí)行上述方法(wait()、notify()、notifyAll())
wait()/notify() 方法只能在 synchronized 代碼塊內(nèi)被調(diào)用,如果調(diào)用 wait()/notify() 方法時(shí),當(dāng)前線程沒有持有對象鎖,會拋出異常 java.lang.IllegalMonitorStateException。
生產(chǎn)者/消費(fèi)者模式應(yīng)用生產(chǎn)者(Producer)生成數(shù)據(jù)的線程
消費(fèi)者(Consumer)使用數(shù)據(jù)的線程
生產(chǎn)者線程和消費(fèi)者線程通過共享隊(duì)列進(jìn)行協(xié)作,
生產(chǎn)者/消費(fèi)者模式在生產(chǎn)者和消費(fèi)者之間加入了一個(gè)橋梁角色,該橋梁角色用于消除線程間處理速度的差異。
Channel 角色持有共享隊(duì)列 Data,對 Producer 角色和 Consumer 角色的訪問執(zhí)行互斥處理,并隱藏多線程實(shí)現(xiàn)。
線程的中斷線程正常結(jié)束于 run() 方法執(zhí)行完畢,但在實(shí)際應(yīng)用中多線程模式往往是死循環(huán),考慮到存在特殊情況需要取消/關(guān)閉線程。Java 使用中斷機(jī)制,通過協(xié)作方式傳遞信息,從而取消/關(guān)閉線程。
中斷的方法public static boolean interrupted() public boolean isInterrupted() public void interrupt()
interrupt() 和 isInterrupted() 是實(shí)例方法,通過線程對象調(diào)用。
interrupted() 是靜態(tài)方法,由當(dāng)前線程 Thread.currentThread() 實(shí)際執(zhí)行。
線程存在 interrupted 中斷狀態(tài)標(biāo)記,用于判斷線程是否中斷。
isInterrupted() 實(shí)例方法返回對應(yīng)線程的中斷狀態(tài)。
interrupted() 靜態(tài)方法返回當(dāng)前線程的中斷狀態(tài),存在副作用清空中斷狀態(tài)。
不同線程狀態(tài)的中斷反應(yīng)
NEW、TERMINATED
調(diào)用 interrupt() 方法不起任何作用
RUNNABLE
調(diào)用 interrupt() 方法,線程正在運(yùn)行,且與 I/O 操作無關(guān),設(shè)置線程中斷狀態(tài)標(biāo)記而已。如果線程等待 I/O 操作,則會進(jìn)行特殊處理。
BLOCKED
調(diào)用 interrupt() 方法無法中斷正在 BLOCKED 狀態(tài)的線程
WAITING、TIMED_WAITING
調(diào)用 interrupt() 方法設(shè)置線程中斷狀態(tài)標(biāo)記,拋出 InterruptedException 異常。這是一個(gè)受檢異常,線程必須進(jìn)行處理。
中斷的使用
對于提供線程服務(wù)的模塊,應(yīng)該封裝取消/關(guān)閉線程方法對外提供接口,而不是交由調(diào)用者自行調(diào)用 interrupt() 方法。
線程狀態(tài)轉(zhuǎn)換綜合圖解結(jié)合線程的方法(Thread 類 + Object 類)來看線程的狀態(tài)轉(zhuǎn)換:
注:
Thread t = new Thread(); Thread 類調(diào)用靜態(tài)方法,t 對象調(diào)用實(shí)例方法。
Object o = new Object(); Object 類調(diào)用靜態(tài)方法,o 對象調(diào)用實(shí)例方法。
Running 表示運(yùn)行中狀態(tài),并非 Thread.State 枚舉類型。
附錄 Runnable 接口和 Callable 接口Runnable 接口提供的 run() 方法返回值為 void
Callable 接口提供的 call() 方法返回值為泛型
Callable 接口常用與配合 Future、FutureTask 類獲取異步執(zhí)行結(jié)果。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/71909.html
摘要:線程可以被稱為輕量級進(jìn)程。一個(gè)守護(hù)線程是在后臺執(zhí)行并且不會阻止終止的線程。其他的線程狀態(tài)還有,和。上下文切換是多任務(wù)操作系統(tǒng)和多線程環(huán)境的基本特征。在的線程中并沒有可供任何對象使用的鎖和同步器。 原文:Java Multi-Threading and Concurrency Interview Questions with Answers 翻譯:并發(fā)編程網(wǎng) - 鄭旭東 校對:方騰飛 多...
摘要:多線程和并發(fā)問題是技術(shù)面試中面試官比較喜歡問的問題之一。線程可以被稱為輕量級進(jìn)程。一個(gè)守護(hù)線程是在后臺執(zhí)行并且不會阻止終止的線程。其他的線程狀態(tài)還有,和。上下文切換是多任務(wù)操作系統(tǒng)和多線程環(huán)境的基本特征。 多線程和并發(fā)問題是 Java 技術(shù)面試中面試官比較喜歡問的問題之一。在這里,從面試的角度列出了大部分重要的問題,但是你仍然應(yīng)該牢固的掌握J(rèn)ava多線程基礎(chǔ)知識來對應(yīng)日后碰到的問題。(...
摘要:并發(fā)編程實(shí)戰(zhàn)水平很高,然而并不是本好書。一是多線程的控制,二是并發(fā)同步的管理。最后,使用和來關(guān)閉線程池,停止其中的線程。當(dāng)線程調(diào)用或等阻塞時(shí),對這個(gè)線程調(diào)用會使線程醒來,并受到,且線程的中斷標(biāo)記被設(shè)置。 《Java并發(fā)編程實(shí)戰(zhàn)》水平很高,然而并不是本好書。組織混亂、長篇大論、難以消化,中文翻譯也較死板。這里是一篇批評此書的帖子,很是貼切。俗話說:看到有這么多人罵你,我就放心了。 然而知...
摘要:此對象在線程受阻塞時(shí)被記錄,以允許監(jiān)視工具和診斷工具確定線程受阻塞的原因。阻塞當(dāng)前線程,最長不超過納秒,返回條件在的基礎(chǔ)上增加了超時(shí)返回。喚醒線程喚醒處于阻塞狀態(tài)的線程。 LockSupport 用法簡介 LockSupport 和 CAS 是Java并發(fā)包中很多并發(fā)工具控制機(jī)制的基礎(chǔ),它們底層其實(shí)都是依賴Unsafe實(shí)現(xiàn)。 LockSupport是用來創(chuàng)建鎖和其他同步類的基本線程阻塞...
摘要:線程線程,是程序執(zhí)行流的最小單元。由于線程之間的相互制約,致使線程在運(yùn)行中呈現(xiàn)出間斷性。線程的狀態(tài)機(jī)線程也有就緒阻塞和運(yùn)行三種基本狀態(tài)。在單個(gè)程序中同時(shí)運(yùn)行多個(gè)線程完成不同的工作,稱為多線程??梢砸暈椴煌€程競爭一把鎖。 進(jìn)程線程協(xié)程 進(jìn)程 進(jìn)程是一個(gè)實(shí)體。每一個(gè)進(jìn)程都有它自己的地址空間, 文本區(qū)域(text region) 數(shù)據(jù)區(qū)域(data region) 堆棧(stack re...
閱讀 2789·2021-11-02 14:42
閱讀 3172·2021-10-08 10:04
閱讀 1193·2019-08-30 15:55
閱讀 1036·2019-08-30 15:54
閱讀 2327·2019-08-30 15:43
閱讀 1688·2019-08-29 15:18
閱讀 871·2019-08-29 11:11
閱讀 2370·2019-08-26 13:52