線程概述
線程與進(jìn)程
進(jìn)程
?每個運(yùn)行中的任務(wù)(通常是程序)就是一個進(jìn)程。當(dāng)一個程序進(jìn)入內(nèi)存運(yùn)行時,即變成了一個進(jìn)程。每一個進(jìn)程都有一定的獨(dú)立功能,進(jìn)程是系統(tǒng)進(jìn)行資源分配與調(diào)度的一個獨(dú)立單元。
- 獨(dú)立性:進(jìn)程是系統(tǒng)中獨(dú)立存在的實(shí)體,它可以擁有自己獨(dú)立的資源,每一個進(jìn)程都有自己私有的地址空間。在沒有進(jìn)程本身允許的條件下,一個用戶進(jìn)程不可以直接訪問其他進(jìn)程的地址空間。
- 動態(tài)性:進(jìn)程與程序的區(qū)別在于,程序只是一序列靜態(tài)指令的集合,而進(jìn)程則是正在執(zhí)行中的程序,擁有自己的生命周期和各種不同的狀態(tài),是動態(tài)產(chǎn)生、變化以及消亡的。
- 并發(fā)性:多個進(jìn)程可以在單個處理器上并發(fā)執(zhí)行,并且不會互相影響。
- 異步性:由于進(jìn)程的相互制約,使進(jìn)程具有執(zhí)行的間斷性,即進(jìn)程按各自獨(dú)立的、 不可預(yù)知的速度向前推進(jìn)。異步性會導(dǎo)致執(zhí)行結(jié)果的不可再現(xiàn)性,為此,在操作系統(tǒng)中必須配置相應(yīng)的進(jìn)程同步機(jī)制。
- 結(jié)構(gòu)性:進(jìn)程包含程序及其相關(guān)數(shù)據(jù)結(jié)構(gòu)。進(jìn)程的實(shí)體包含進(jìn)程控制塊(PCB),程序塊、數(shù)據(jù)塊和堆棧,又稱為進(jìn)程映像。
線程
?線程是進(jìn)程的執(zhí)行單元,是進(jìn)程的組成部分,一個進(jìn)程可以擁有多個線程,一個線程必須有一個父進(jìn)程。線程可以擁有自己的堆棧,自己的程序計(jì)數(shù)器以及自己的局部變量,但不擁有系統(tǒng)資源,它與父進(jìn)程的其它線程共享該進(jìn)程的全部資源。
?一個線程可以創(chuàng)建和銷毀另一個線程,同一個進(jìn)程中的多個線程之間可以并發(fā)執(zhí)行。
?線程是程序中一個單一的順序控制流程。進(jìn)程內(nèi)一個相對獨(dú)立的、可調(diào)度的執(zhí)行單元,是系統(tǒng)獨(dú)立調(diào)度和分派CPU的基本單位。在單個程序中同時運(yùn)行多個線程完成不同的工作,稱為多線程。
- 進(jìn)程之間不能共享內(nèi)存,但線程之間共享內(nèi)存非常容易。
- 系統(tǒng)創(chuàng)建一個進(jìn)程需要為該進(jìn)程分配獨(dú)立的內(nèi)存空間,并分配大量的相關(guān)資源,但創(chuàng)建線程則代價小得多,因此使用多線程來實(shí)現(xiàn)多任務(wù)比使用多進(jìn)程的效率高。
- 因?yàn)榫€程劃分的尺度小于進(jìn)程,使得多線程程序的并發(fā)性高。
線程實(shí)現(xiàn)
繼承Thread類
- 定義Thread類的子類,并重寫run()方法,該run()方法的方法體就代表了線程要完成的任務(wù)即線程的執(zhí)行體;
- 創(chuàng)建Thread子類的實(shí)例,即創(chuàng)建線程對象;
-
調(diào)用線程對象的start()方法來啟動該線程。(注意,不是直接調(diào)用對象的run()方法,調(diào)用對象的run()方法,其實(shí)就相當(dāng)于普通的方法調(diào)用,并不會創(chuàng)建線程);
通過繼承Thread類實(shí)現(xiàn)線程.png
線程執(zhí)行結(jié)果.png
?從圖中可以看到,程序創(chuàng)建了三個線程,包含一個主線程和兩個子線程。并且可以看到,線程輸出并不是連續(xù)的,這是因?yàn)榫€程的執(zhí)行是基于系統(tǒng)資源調(diào)度執(zhí)行的。
?從圖中可以看出,sum變量不是連續(xù)的。使用繼承Thread的方法來創(chuàng)建線程類時,多個線程之間無法共享線程類的實(shí)例變量;因?yàn)槊看蝿?chuàng)建線程對象時都是需要創(chuàng)建一個MyThread對象,每個對象都包含自己的實(shí)例變量。
實(shí)現(xiàn)Runnable接口
- 定義實(shí)現(xiàn)Runnable接口的實(shí)現(xiàn)類,并重寫接口的run()方法,該run()方法的方法體就代表了線程要完成的任務(wù)即線程的執(zhí)行體;
- 創(chuàng)建Runnable實(shí)現(xiàn)類的實(shí)例,并以此實(shí)例作為Thread的target來創(chuàng)建Thread對象,該Thread對象才是真正的線程對象;
-
調(diào)用Thread對象的start()方法來啟動該線程。(注意,不是直接調(diào)用Runnable對象的run()方法,調(diào)用Runnable對象的run()方法,其實(shí)就相當(dāng)于普通的方法調(diào)用,并不會創(chuàng)建線程);
通過實(shí)現(xiàn)Runnable接口實(shí)現(xiàn)線程.png
線程執(zhí)行結(jié)果.png
?從圖中可以看出,sum變量時連續(xù)的。使用實(shí)現(xiàn)Runnable的方法來創(chuàng)建線程類時,多個線程之間可以共享線程類的實(shí)例變量;因?yàn)樵谶@種方式下,程序所創(chuàng)建的Runnable對象只是線程的target,而多個線程可以共享同一個target,所以多個線程可以共享同一個線程類(線程的target類)的實(shí)例變量。
- String getName() : 獲取當(dāng)前線程的名稱;
- void run() : 線程的執(zhí)行體,線程需要完成的任務(wù)都在該方法中實(shí)現(xiàn);
- void start() :線程對象通過調(diào)用此方法來啟動線程;
- Thread Thread.currentThread() : 返回當(dāng)前正在執(zhí)行的線程;
通過Callable和Future創(chuàng)建線程
- 創(chuàng)建Callable接口的實(shí)現(xiàn)類,并實(shí)現(xiàn)call()方法,該call()方法將作為線程執(zhí)行體,并且有返回值;
- 創(chuàng)建Callable實(shí)現(xiàn)類的實(shí)例,使用FutureTask類來包裝Callable對象,該FutureTask對象封裝了該Callable對象的call()方法的返回值;
- 使用FutureTask對象作為Thread對象的target創(chuàng)建并啟動新線程;
-
調(diào)用FutureTask對象的get()方法來獲得子線程執(zhí)行結(jié)束后的返回值,注意不是直接調(diào)用Callable對象的call()方法獲取返回值,Callable對象的call()方法為線程的執(zhí)行體被調(diào)用;
通過Callable和Futrue創(chuàng)建線程.png
線程執(zhí)行結(jié)果.png
?從圖中可以看到當(dāng)主線程的sum變量循環(huán)到20時,程序啟動以FutureTask對象為target的線程,然后通過調(diào)用FutureTask對象的get()方法來獲得call()方法的返回值。該方法將導(dǎo)致程序的主進(jìn)程被阻塞,直到call()方法結(jié)束并返回為止。
Callable 接口方法
- V call() : 線程的執(zhí)行體,線程需要完成的任務(wù)都在該方法中實(shí)現(xiàn),并帶有返回值;
Future接口方法
- V get() : 返回Callable對象里call()方法的返回值。調(diào)用該方法將導(dǎo)致程序阻塞,必須等待子線程結(jié)束后才回得到返回值;
- V get(long timeout, TimeUnit unit) : 返回Callable對象里call()方法的返回值。調(diào)用該方法將導(dǎo)致程序最多阻塞timeout和unit指定時間,如果經(jīng)過指定時間后,Callable任務(wù)依然沒有返回值,則拋出TimeoutException異常;
- boolean cancel(boolean mayInterruptIfRunning) : 試圖取消Future里關(guān)聯(lián)的Callable任務(wù);
- boolean isCancelled() : 如果在Callable任務(wù)正常完成前被取消,返回true;
- boolean isDone() : 如果Callable任務(wù)已完成,返回true;
創(chuàng)建線程三種方式的比較
采用繼承Thread類創(chuàng)建線程的優(yōu)缺點(diǎn)
- 劣勢:因?yàn)榫€程類已經(jīng)繼承了Thread類,不能在繼承別的父類;
- 優(yōu)勢:編寫簡單,如果訪問當(dāng)前線程,則無需使用Thread.currentThrend()方法,直接使用this就可獲得當(dāng)前線程;
采用實(shí)現(xiàn)Runnable,Callable接口創(chuàng)建線程的優(yōu)缺點(diǎn)
- 優(yōu)勢:線程類只實(shí)現(xiàn)了Runnable接口或Callable接口,還可以繼續(xù)繼承其他類;
- 優(yōu)勢:在這種情況下,多個進(jìn)程可以共享同一個target對象,所以非常適合多個相同的線程來處理同一份資源的情況,從而可以將CPU、代碼和數(shù)據(jù)分開,形成清晰的模型,較好的體現(xiàn)了面向?qū)ο蟮乃枷耄?/li>
- 劣勢:編程稍微復(fù)雜,如果需要訪問當(dāng)前線程,需要使用Thread.currentThrend()方法獲??;
線程狀態(tài)
- 新建狀態(tài)(NEW)
- 可運(yùn)行狀態(tài)(RUNNABLE)
- 阻塞狀態(tài)(BLOCKED)
- 等待狀態(tài)(WAITING)
- 計(jì)時等待狀態(tài)(TIMED_WAITING)
- 終止?fàn)顟B(tài)(TERMINATED)
新建狀態(tài)(NEW)
?當(dāng)用new關(guān)鍵字創(chuàng)建一個新線程時,該線程處于新建狀態(tài)。此時他和其他的Java對象一樣,僅僅由JVM為其分配內(nèi)存,并初始化成員變量的值,此時線程對象沒有表現(xiàn)出線程的動態(tài)特征,程序也不會執(zhí)行線程的執(zhí)行體。
可運(yùn)行狀態(tài)(RUNNABLE)
?當(dāng)線程對象調(diào)用了start()方法后,線程處于runnable狀態(tài),JVM為其創(chuàng)建方法調(diào)用棧和程序計(jì)數(shù)器。處于runnable狀態(tài)的線程,可能正在運(yùn)行也可能沒有運(yùn)行,這取決于JVM里線程調(diào)度器的調(diào)度,當(dāng)線程獲得CPU時間片時,線程執(zhí)行。
?當(dāng)一個線程開始運(yùn)行時,它不可能處于一直運(yùn)行的狀態(tài)(除非它的線程執(zhí)行體足夠短,瞬間就執(zhí)行結(jié)束了)。線程在運(yùn)行過程中需要被中斷,目的是讓其他線程獲得執(zhí)行的機(jī)會,線程調(diào)度的細(xì)節(jié)依賴于操作系統(tǒng)提供的服務(wù)。
阻塞與(計(jì)時)等待狀態(tài)(BLOCKED,WAITING,TIMED_WAITING)
?當(dāng)線程處于阻塞或者等待狀態(tài)時,它暫時不活動。直到線程調(diào)度器重新激活它。細(xì)節(jié)取決于它是怎么達(dá)到非活動狀態(tài)的。
- 當(dāng)一個線程試圖獲取一個內(nèi)部的對象鎖(而不是java.util.concurrent庫中的鎖),而該鎖被其他線程持有時,則該線程進(jìn)入阻塞狀態(tài)。當(dāng)所有其他線程釋放該鎖,并且線程調(diào)度器允許本線程持有它的時候,該線程將變成非阻塞狀態(tài)。
- 當(dāng)線程等待另一個線程通知調(diào)度器一個條件時,它自己進(jìn)入等待狀態(tài)。在調(diào)用Object.wait方法或Thread.join方法,或者是等待java.util.concurrent庫中的Lock或Condition時,就會出現(xiàn)這種情況。
- 有幾個方法有一個超時參數(shù)。調(diào)用它們導(dǎo)致線程進(jìn)入計(jì)時等待狀態(tài)。這一狀態(tài)將一直保持到超時期滿或者接收到適當(dāng)?shù)耐ㄖ?。帶有超時參數(shù)的方法有Thread.sleep和Object.wait、Thread.join、Lock.tryLock以及Condition.await的計(jì)時版。
終止?fàn)顟B(tài)(TERMINATED)
?線程因如下原因之一而被終止:(當(dāng)然還有一種調(diào)用stop()方法,不過該方法已過時,不建議調(diào)用)
- 因?yàn)閞un()方法或call()方法執(zhí)行完成,結(jié)束后線程就自然死亡。
-
因?yàn)橐粋€沒有捕獲的Exception或Error而意外死亡。
線程狀態(tài)轉(zhuǎn)換圖(來自一哥們).png
Thread 方法
- void join() : 等待終止指定的線程;
- void join(long millis) : 等待指定的線程死亡或者經(jīng)過指定的毫秒數(shù);
- Thread.State getState() : 得到這一線程的狀態(tài);NEW,RUNNABLE,BLOCKED,WAITING,TIMED_WAITING或者TERMINATED之一;
線程屬性
線程優(yōu)先級
?每個線程執(zhí)行都有一定的優(yōu)先級,優(yōu)先級高的獲得較多的執(zhí)行機(jī)會,優(yōu)先級低的執(zhí)行機(jī)會先對較少。默認(rèn)情況下,一個線程繼承它的父線程優(yōu)先級;默認(rèn)情況下,main線程具有普通優(yōu)先級,由main創(chuàng)建的線程也具普通優(yōu)先級。
?Thread類提供setPriority(int newPriority),getPriority()方法來設(shè)置和獲取指定線程的優(yōu)先級??梢詫?yōu)先級設(shè)置為MIN_PRIORITY(Thread類中定義為1)與MAX_PRIORITY(定義為10)之間的任何值。默認(rèn)NORM_PRIORITY被定義5。
?每當(dāng)線程調(diào)度器有機(jī)會選擇新線程時,他首先選擇具有高優(yōu)先級的線程。但是線程優(yōu)先級是高度依賴于操作系統(tǒng)的。不同操作系統(tǒng)上的優(yōu)先級并不相同,而且也不能很好的和Java的10個優(yōu)先級對應(yīng)。(例如Windows有7個優(yōu)先級。一些Java優(yōu)先級將映射到相同的操作系統(tǒng)優(yōu)先級。在Oracle為Linux提供的Java虛擬機(jī)中,線程的優(yōu)先級被忽略——所有的線程具有相同的優(yōu)先級。(來自Java核心技術(shù) 卷I))。所以應(yīng)盡量避免直接為線程指定優(yōu)先級。
Thread 方法
- void setPriority(int newPriority) : 設(shè)置線程的優(yōu)先級。優(yōu)先級必須在Thread.MIN_PRIORITY與Thread.MAX_PRIORITY之間,一般使用Thread.MIN_PRIORITY優(yōu)先級。
- static int MIN_PRIORITY : 線程的最小優(yōu)先級,最小優(yōu)先級的值為1。
- static int NORM_PRIORITY : 線程的默認(rèn)優(yōu)先級,默認(rèn)優(yōu)先級的值為5。
- static int MAX_PRIORITY : 線程的最大優(yōu)先級,最大優(yōu)先級的值為10。
- static native void yield() : 導(dǎo)致當(dāng)前執(zhí)行線程處于讓步狀態(tài)。如果其他的可運(yùn)行線程具有至少與此線程同樣高的優(yōu)先級,那么這些線程接下來會被調(diào)度。
守護(hù)線程
?可以通過調(diào)用setDaemon(true)將線程轉(zhuǎn)換為守護(hù)線程。守護(hù)線程的唯一用途就是為其它線程提供服務(wù)。當(dāng)只剩下守護(hù)線程時虛擬機(jī)就退出了,由于如果只剩下守護(hù)線程,就沒必要繼續(xù)運(yùn)行程序了。
?守護(hù)線程應(yīng)該永遠(yuǎn)不去訪問固有資源,如文件、數(shù)據(jù)庫,因?yàn)樗鼤谌魏螘r候甚至在一個操作的中間發(fā)生中斷。
Thread 方法
- void setDaemon(boolean isDaemon) : 標(biāo)示該線程為守護(hù)線程或者用戶線程。這一方法必須在線程啟動之前調(diào)用。
- boolean isDaemon() : 判斷線程是否是守護(hù)線程。
常用方法
- public synchronized void start() : 使該線程開始執(zhí)行;Java 虛擬機(jī)調(diào)用該線程的 run 方法。
- public void run() : 線程的執(zhí)行體,線程的執(zhí)行任務(wù)在該方法完成。
- public final synchronized void setName(String name) : 設(shè)置線程的名稱。
- public final void setPriority(int newPriority) : 設(shè)置線程的優(yōu)先級。
- public final void setDaemon(boolean on) : 將該線程設(shè)置為守護(hù)線程或者用戶線程。守護(hù)線程和用戶線程的區(qū)別在于:守護(hù)線程依賴于創(chuàng)建它的線程,而用戶線程則不依賴。必須在thread.start()之前設(shè)置,否則會拋出一個IllegalThreadStateException異常
- public final void join() : 等待該線程終止。
- public final synchronized void join(long millis) : 等待該線程終止的時間最長為 millis 毫秒。
- public final synchronized void join(long millis, int nanos) : 等待該線程終止,當(dāng) 999999 > nanos > 500000 時,最長等待時間為 millis + 1;當(dāng) millis = 0 && nanos != 0 ,最長等待時間為1毫秒。
- public void interrupt() : 向線程發(fā)送中斷請求。線程的中斷狀態(tài)將被設(shè)置為true。如果目前該線程被一個sleep調(diào)用阻塞,那么將拋出InterruptedException異常。
- public static boolean interrupted() : 測試當(dāng)前線程(即正在執(zhí)行這一命令的線程)是否被中斷。這一調(diào)用會產(chǎn)生副作用——它將當(dāng)前線程的中斷狀態(tài)設(shè)置為false。
- public boolean isInterrupted() : 測試線程是否被終止。這一調(diào)用不會改變線程的中斷狀態(tài)。
- public final native boolean isAlive() : 測試線程是否處于活動狀態(tài)(線程處于正在運(yùn)行或準(zhǔn)備開始運(yùn)行的狀態(tài))。
- public static native void sleep(long millis) : 在指定的毫秒數(shù)內(nèi)讓當(dāng)前正在執(zhí)行的線程休眠(暫停執(zhí)行),此操作受到系統(tǒng)計(jì)時器和調(diào)度程序精度和準(zhǔn)確性的影響。有一點(diǎn)要非常注意,sleep方法不會釋放鎖,也就是說如果當(dāng)前線程持有對某個對象的鎖,則即使調(diào)用sleep方法,其他線程也無法訪問這個對象。如果調(diào)用了sleep方法,必須捕獲InterruptedException異?;蛘邔⒃摦惓O蛏蠈訏伋?。當(dāng)線程睡眠時間滿后,不一定會立即得到執(zhí)行,因?yàn)榇藭r可能CPU正在執(zhí)行其他的任務(wù)。所以說調(diào)用sleep方法相當(dāng)于讓線程進(jìn)入阻塞狀態(tài)。
- public static void sleep(long millis, int nanos) : 在指定的毫秒數(shù)內(nèi)讓當(dāng)前正在執(zhí)行的線程休眠(暫停執(zhí)行),當(dāng) 999999 > nanos >= 500000 時,暫停時間為 millis + 1 毫秒,當(dāng) nanos != 0 && millis == 0 暫停時間為1毫秒。
- public static native void yield() : 導(dǎo)致當(dāng)前執(zhí)行線程處于讓步狀態(tài)。如果有其他可運(yùn)行的線程具有至少與此線程同樣高的優(yōu)先級,那么這些線程接下來會被調(diào)度。調(diào)用yield方法會讓當(dāng)前線程交出CPU權(quán)限,讓CPU去執(zhí)行其他的線程。它跟sleep方法類似,同樣不會釋放鎖。但是yield不能控制具體的交出CPU的時間。調(diào)用yield方法并不會讓線程進(jìn)入阻塞狀態(tài),而是讓線程重回就緒狀態(tài),它只需要等待重新獲取CPU執(zhí)行時間,這一點(diǎn)是和sleep方法不一樣的。
- public static native Thread currentThread() : 返回當(dāng)前執(zhí)行線程的Thread對象。
線程面試
什么是線程?
?線程是操作系統(tǒng)能夠進(jìn)行運(yùn)算調(diào)度的最小單位,它被包含在進(jìn)程之中,是進(jìn)程中的實(shí)際運(yùn)作單位。
線程與進(jìn)程的區(qū)別?
?進(jìn)程是一個獨(dú)立的運(yùn)行環(huán)境,它可以被看作是一個程序或者一個應(yīng)用。而線程是在進(jìn)程中執(zhí)行的一個任務(wù)。線程是進(jìn)程的子集,一個進(jìn)程可以有很多線程,每條線程并行執(zhí)行不同的任務(wù)。不同的進(jìn)程使用不同的內(nèi)存空間,而所有的線程共享一片相同的內(nèi)存空間。別把它和棧內(nèi)存搞混,每個線程都擁有單獨(dú)的棧內(nèi)存用來存儲本地數(shù)據(jù)。
如何在Java中實(shí)現(xiàn)線程?
- 繼承Thread類;
- 實(shí)現(xiàn)Runnable接口;
- 通過Callable和Future創(chuàng)建線程;
Thread 類中的start() 和 run() 方法有什么區(qū)別?
?start()方法被用來啟動新創(chuàng)建的線程,而且start()內(nèi)部調(diào)用了run()方法,這和直接調(diào)用run()方法的效果不一樣。當(dāng)你調(diào)用run()方法的時候,只會是在原來的線程中調(diào)用,沒有新的線程啟動,start()方法才會啟動新線程。
Runnable和Callable有什么不同?
?Runnable和Callable都代表那些要在不同的線程中執(zhí)行的任務(wù)。Runnable從JDK1.0開始就有了,Callable是在JDK1.5增加的。它們的主要區(qū)別是Callable的 call() 方法可以返回值和拋出異常,而Runnable的run()方法沒有這些功能。Callable可以返回裝載有計(jì)算結(jié)果的Future對象。
Thread類的sleep()方法和對象的wait()方法都可以讓線程暫停執(zhí)行,它們有什么區(qū)別?
?sleep()方法是線程類(Thread)的靜態(tài)方法,調(diào)用此方法會讓當(dāng)前線程暫停執(zhí)行指定的時間,將執(zhí)行機(jī)會(CPU)讓給其他線程,但是對象的鎖依然保持,因此休眠時間結(jié)束后會自動恢復(fù)(線程回到就緒狀態(tài))。wait()是Object類的方法,調(diào)用對象的wait()方法導(dǎo)致當(dāng)前線程放棄對象的鎖(線程暫停執(zhí)行),進(jìn)入對象的等待池,只有調(diào)用對象的notify()方法(或notifyAll()方法)時才能喚醒等待池中的線程進(jìn)入等鎖池,如果線程重新獲得對象的鎖就可以進(jìn)入就緒狀態(tài)。
線程的sleep()方法和yield()方法有什么區(qū)別?
- sleep()方法給其他線程運(yùn)行機(jī)會時不考慮線程的優(yōu)先級,因此會給低優(yōu)先級的線程以運(yùn)行的機(jī)會;yield()方法只會給相同優(yōu)先級或更高優(yōu)先級的線程以運(yùn)行的機(jī)會;
- 線程執(zhí)行sleep()方法后轉(zhuǎn)入阻塞狀態(tài),而執(zhí)行yield()方法后轉(zhuǎn)入就緒狀態(tài);
- sleep()方法聲明拋出InterruptedException,而yield()方法沒有聲明任何異常;
- sleep()方法比yield()方法(跟操作系統(tǒng)CPU調(diào)度相關(guān))具有更好的可移植性。
????整理文章主要為了自己日后復(fù)習(xí)用,文章中可能會引用到別的博主的文章內(nèi)容,如涉及到博主的版權(quán)問題,請博主聯(lián)系我。






