一、 概念問答
問:什么是進程驱还?
答: 進程是操作系統(tǒng)進行資源(cpu,內(nèi)存空間,磁盤IO)分配的最小單位,進程與進程之前相互獨立义锥。
問:什么是線程?
答: 線程是指進程中的一個執(zhí)行流程,一個進程可以運行多個線程。Java中線程是指java.lang.Thread類的一個實例碉熄。 線程是CPU調(diào)動的最小單位,不能獨立于進程存在肋拔。啟動一個應(yīng)用程序锈津,必然至少有一個線程。真正執(zhí)行任務(wù)的是線程凉蜂。(為了合理分配資源琼梆,一般情況下每個進程都會有最大線程數(shù)。Linux 一個進程中最多可以開1000個線程窿吩;Window 一個進程中最多可以開1200個線程茎杂。)
問:線程和進程之間的關(guān)系?
答: 一個進程可以有多個線程纫雁,在同一個進程中蛉顽,線程可以共享該進程的資源(cpu,內(nèi)存,磁盤···)。
問:CPU核心數(shù)和線程數(shù)的關(guān)系先较?
答: 一般情況下携冤,核心數(shù)和線程數(shù)是一比一的關(guān)系,即在同一個時間內(nèi)闲勺,一個內(nèi)核只能運行一個線程曾棕。那么問題來了:我們平時在開發(fā)中,經(jīng)常會開很多個線程菜循,那么這又是怎么回事呢?此時就需要引入另外一個概念:CPU時間片輪轉(zhuǎn)機制翘地,也叫RR調(diào)度
問:什么是CPU時間片輪轉(zhuǎn)機制?
答: 將CPU的時間進行切片癌幕,每個進程分配一個時間段衙耕;然后CPU輪流去執(zhí)行某個時間段。(一般情況下CPU執(zhí)行一個指令的時間是0.6納秒(1s=10億納秒)勺远;普通人的時間感知為0.1s,所以CPU在切換時間片的時候橙喘,我們是無法感知的,因此給我們的錯覺就是胶逢,每個任務(wù)都是連續(xù)執(zhí)行的厅瞎。這就是為什么我們可以開大于內(nèi)核數(shù)量的線程的原因)
問:什么叫并行?
答: 可以同時運行的任務(wù)數(shù)初坠,叫并行和簸。
例如:8核CPU,在同一時間點可以同時執(zhí)行8個任務(wù)碟刺。
問:什么叫并發(fā)量锁保?
答: 在單位時間內(nèi),能夠執(zhí)行的任務(wù)數(shù)。
例如:1核CPU爽柒,在1秒鐘之內(nèi)吴菠,可以執(zhí)行100個任務(wù);他的并發(fā)量就是100霉赡。
問:什么叫多線程開發(fā)?
答: 顧名思義幔托,在同一個進程(應(yīng)用)中同時運行多個線程穴亏。使用多線程開發(fā)的好處就是,使代碼模塊化重挑,異步化嗓化,簡單化。與之相對的壞處就是容易出現(xiàn)線程安全問題谬哀。
問:線程安全是什么刺覆?安全線程又是什么?
答:
- 線程安全就是線程同步的意思史煎,就是當(dāng)一個程序?qū)σ粋€線程安全的方法或者語句進行訪問的時候谦屑,其他的不能再對他進行操作了,必須等到這次訪問結(jié)束以后才能對這個線程安全的方法進行訪問篇梭。
- 安全線程就是氢橙,如果你的代碼所在的進程中有多個線程在同時運行,而這些線程可能會同時運行這段代碼恬偷。如果每次運行結(jié)果和單線程運行的結(jié)果是一樣的悍手,而且其他的變量的值也和預(yù)期的是一樣的,說明這些線程就是安全的袍患。
問:什么情況下可能出現(xiàn)線程安全坦康?
答: 當(dāng)線程與線程之間存在資源競爭的時候,容易出現(xiàn)線程安全問題诡延。
問:如何解決線程安全問題滞欠?
答: 使用鎖(synchronized,volatile)肆良。
問:如何啟動一個線程
答: 啟動線程有兩種方式:Thread和Runnable(JDK官方明確提出仑撞,啟動線程的方法只有兩種);
Thread用法:
//創(chuàng)建一個線程對象
class Thread1 extends Thread{
public void run() {
//do something
}
}
//調(diào)用類
public class Main {
public static void main(String[] args) {
//啟動線程
new Thread1().start();
}
}
Runnable用法:
//實現(xiàn) Runnable接口
public class Thread2 implements Runnable{
public void run() {
//do someThing
}
}
//調(diào)用類
public class Main {
public static void main(String[] args) {
//啟動線程2
new Thread(new Thread2()).start();
}
}
問:Thread和Runnable的區(qū)別是什么妖滔?
答: 在Java中隧哮,Thread是對線程的抽象;而Runnable是對任務(wù)(業(yè)務(wù)邏輯)的抽象
問:啟動線程后座舍,又該如何停止線程呢沮翔?
答: 使用suspend(),stop(),interrupt(),isInterrupted()和靜態(tài)方法interred()
- suspend()掛起:讓一個線程進行一次上下文切換,使其從可運行狀態(tài),轉(zhuǎn)變?yōu)閽炱馉顟B(tài)采蚀。此時不會釋放資源也不會釋放鎖疲牵。占著資源和鎖進入到睡眠狀態(tài),容易引發(fā)死鎖問題榆鼠。官方不建議使用纲爸,已廢棄!
- stop()停止:強制干掉當(dāng)前線程妆够,可能會造成當(dāng)前線程資源無法釋放识啦,也可能會文件寫到一半時突然中止,導(dǎo)致文件不完整神妹。該方法過于暴力颓哮,官方不建議使用,已廢棄鸵荠!
- interrupt()中斷:對線程發(fā)起中斷冕茅,標(biāo)識該線程應(yīng)該被中斷了,而非立即中斷蛹找。非強制性姨伤!線程可以忽視該標(biāo)識。
- isInterrupted()是否中斷:檢查當(dāng)前線程是否被中斷
- static interrupted():檢查當(dāng)前線程是否被中斷庸疾,然后將中斷標(biāo)識修改為false姜挺;
問:什么是守護線程?
答: 守護線程是指為其他線程服務(wù)的線程彼硫。在JVM中炊豪,所有非守護線程都執(zhí)行完畢后,無論有沒有守護線程拧篮,虛擬機都會自動退出词渤。因此,JVM退出時串绩,不必關(guān)心守護線程是否已結(jié)束缺虐。
二、Thread詳解(Android API 27版本)
根據(jù)上面的知識點我們知道礁凡,Thread類是Java中對線程的唯一抽象高氮。那么我們對這個Thread類是否足夠了解呢?接下來我們就來仔細(xì)探討一下這個Thread類顷牌。
線程是什么剪芍,我們已經(jīng)知道了:線程是進程中的一個執(zhí)行流程。既然是執(zhí)行流程窟蓝,那就有開始和結(jié)束罪裹;那么線程從開始到結(jié)束究竟是如何的呢?我們看下圖:
從上圖可知,一個完整的線程總共5個狀態(tài),分別是:新建状共、就緒套耕、運行、阻塞峡继、死亡冯袍。那么這時候就有個疑問了,為什么start()之后沒有直接進入運行狀態(tài)碾牌,而是到了就緒狀態(tài)呢康愤?這就是因為有CPU時間片輪轉(zhuǎn)機制,當(dāng)start()之后小染,線程進入到就緒狀態(tài)(可運行狀態(tài))翘瓮,只有當(dāng)CPU給該線程分配了運行時間(獲得執(zhí)行權(quán))贮折,該線程才能進入到運行狀態(tài)裤翩。
每個狀態(tài)都對應(yīng)著一定的操作,接下來我們來看看這些操作是什么意思调榄,有什么作用:
start(): 啟動線程
通過觀察圖片上的源碼踊赠,我們可以得到如下幾個信息:
在716行的注釋中This method is not invoked for the main method thread or "system" 這句話表明,方法不會被主線程或者系統(tǒng)線程調(diào)用每庆,因此該方法就是專門給我們開發(fā)者使用的筐带。
-
在 723行和724行這兩行代碼表明:一個線程只能啟動一次,多次啟動會拋狀態(tài)異常!
-
733行:這一行是真正啟動線程的代碼缤灵。這是一個native方法
join(): 該方法的作用就是同步伦籍,它可以使并行執(zhí)行的線程改為順序執(zhí)行。也可以改變線程的執(zhí)行順序腮出。比如帖鸦,當(dāng)前正在執(zhí)行main線程,此時a線程調(diào)用了join()函數(shù)胚嘲,那么main函數(shù)就會放棄當(dāng)前cpu的控制權(quán)作儿,并返回a線程繼續(xù)執(zhí)行,直到a線程執(zhí)行完成馋劈,才執(zhí)行main線程攻锰。具體代碼如下:
會先執(zhí)行main線程妓雾,main線程執(zhí)行結(jié)束之后娶吞,然后執(zhí)行A線程,A線程執(zhí)行結(jié)束之后械姻,然后執(zhí)行B線程寝志。
調(diào)用join()函數(shù)之后:因為在main線程中執(zhí)行了threadA.join(),所以main線程進入就緒狀態(tài),線程A開始執(zhí)行,此時在線程A中又調(diào)用了threadB.join(),所以線程A也進入到了就緒狀態(tài)材部,線程B開始執(zhí)行毫缆;當(dāng)線程B執(zhí)行完成之后,線程A才能繼續(xù)執(zhí)行乐导。線程A執(zhí)行完成之后苦丁,main線程才能繼續(xù)執(zhí)行。這樣就達(dá)到了改變線程的執(zhí)行順序的目的物臂。
yield: 讓當(dāng)前(調(diào)用yeild())的線程讓出CPU的占有權(quán)旺拉,當(dāng)前線程進入到就緒 狀態(tài)。操作系統(tǒng)重新分配CPU使用權(quán)(此時雖然讓出了CPU的使用權(quán)棵磷,但是不會釋放鎖)蛾狗。此時當(dāng)前線程依然可能被分配到CPU的使用權(quán),從而重新進入到運行狀態(tài)仪媒。
sleep():
wait():
interrupt(): 對線程發(fā)起中斷沉桌,標(biāo)識該線程應(yīng)該被中斷了,而非立即中斷算吩。非強制性留凭!線程可以忽視該標(biāo)識。
stop(): 強制干掉當(dāng)前線程偎巢,可能會造成當(dāng)前線程資源無法釋放蔼夜,也可能會文件寫到一半時突然中止,導(dǎo)致文件不完整压昼。該方法過于暴力求冷,官方不建議使用,已廢棄窍霞!
notify():
notifyAll():
setDaemon(boolean on) 將線程標(biāo)記為守護線程匠题。使用方法:
Thread t = new Thread();
t.setDaemon(true);//setDaemon(),在start()之前調(diào)用
t.start();
三、線程安全
synchrond: synchronied關(guān)鍵字也就是我們所謂的鎖官撼,為了保證線程安全梧躺,經(jīng)常會用到它。synchronized鎖的內(nèi)容主要是對象傲绣,所以稱之為對象鎖掠哥,也叫內(nèi)置鎖。其主要用法如下:
輸出結(jié)果為:順序執(zhí)行
在同一個實例中续搀,多個線程同時調(diào)用funName2()和funName3():
輸出結(jié)果為:同時執(zhí)行
輸出結(jié)果為:順序執(zhí)行
volatile: