[TOC]
進程與線程
進程是程序運行資源分配的最小單位。
進程是操作系統(tǒng)進行資源分配的最小單位,其中資源包括CPU、內(nèi)存空間、磁盤等,同一進程中的多條線程共享該進程中的全部系統(tǒng)資源,而進程和進程之間是相互獨立的。進程是具有一定獨立功能的程序關(guān)于某個數(shù)據(jù)集合上的一次運行活動,進程是系統(tǒng)進行資源分配和調(diào)度的一個獨立單位。進程分為系統(tǒng)進程和用戶進程,凡是用于完成操作系統(tǒng)的各種功能的進程就是系統(tǒng)進程;用戶進程就是所有由你啟動的進程。
線程是CPU調(diào)度的最小單位,必須依賴于進程而存在。
線程是進程的一個實體,是CPU調(diào)度和分派的基本單位,它是比進程更小的能獨立運行的基本單位。線程自己基本上不擁有系統(tǒng)資源,只擁有一點在運行中必不可少的資源,比如程序計數(shù)器(一組寄存器和棧),但是它可與同屬一個進程的其他線程共享進程所有的全部資源。
任何一個程序都要創(chuàng)建線程,特別是Java,不管任何程序都要啟動一個main函數(shù)的主線程。JavaWeb開發(fā)里面的定時任務(wù)、定時器、JSP和Servlet、異步消息處理機制、遠程訪問接口RM等,任何一個監(jiān)聽事件等都離不開線程和并發(fā)的知識。
CPU與線程
CPU核心數(shù)與線程數(shù)的關(guān)系
目前主流CPU有雙核、三核和四核,六核也在2010年發(fā)布,增加核心數(shù)目就是為了增加線程數(shù)目,因為操作系統(tǒng)是通過線程來執(zhí)行任務(wù)的,一般情況下它們是1:1的關(guān)系,也就是說四核CPU一般擁有四個線程,但Intel引入超線程技術(shù)后,使核心數(shù)與線程數(shù)形成1:2的關(guān)系。
CPU時間片輪轉(zhuǎn)機制
我們平時在開發(fā)的時候,感覺并沒有受CPU核心數(shù)的限制,想啟動線程就啟動線程,哪怕是在單核CPU上,為什么?這就要靠CPU的時間片輪轉(zhuǎn)機制了。
時間片輪轉(zhuǎn)調(diào)度是一種最古老、最簡單、最公平且使用最廣的算法,又稱RR調(diào)度。每個進程被分配一個時間段,稱作它的時間片,即該進程允許運行的時間。當該進程的時間片運行完以后,將讓出CPU執(zhí)行權(quán),給其他進程使用。
并行與并發(fā)
并行:指應(yīng)用能夠同時執(zhí)行不同的任務(wù)。想象一條高速公路,有四個車道,那么此條高速公路可以并排行駛小于等于四輛車。CPU也是這個原理,核心數(shù)或者線程數(shù)就相當于高速公路上的車道,能同時處理的任務(wù)需要線程數(shù)或者核心數(shù)支持。
并發(fā):指應(yīng)用能夠交替執(zhí)行不同的任務(wù)。當談?wù)摬l(fā)的時候,一定要加個單位時間。比如單CPU核心下執(zhí)行多線程并非是同時執(zhí)行多個任務(wù),而是CPU以不可察覺到的速度在進行時間片輪轉(zhuǎn)。
Java里的線程
Java是一門支持多線程的語言,運行中的Java程序至少有一個主線程。
啟動線程的方式
- 繼承Thread
public class MyThread extends Thread {
@Override
public void run() {
System.out.println("執(zhí)行線程--------------");
}
}
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
}
- 實現(xiàn)Runnable(實現(xiàn)Callable,這種方式其實可以看做是實現(xiàn)Runnable,不過它能拿到異步的返回結(jié)果)
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("執(zhí)行線程--------------");
}
}
public static void main(String[] args) {
Thread thread = new Thread(new MyRunnable());
thread.start();
}
public class MyCallable implements Callable {
@Override
public Object call() throws Exception {
System.out.println("執(zhí)行線程--------------");
return 1;
}
}
public static void main(String[] args) {
//FutureTask實現(xiàn)了RunnableFuture,RunnableFuture實現(xiàn)了Runnable,所以可以看做是實現(xiàn)Runnable啟動線程。
FutureTask<Callable> future = new FutureTask(new MyCallable());
Thread thread = new Thread(future);
thread.start();
}
線程的結(jié)束
run方法執(zhí)行完
run方法中拋出了一個未處理的異常導(dǎo)致線程結(jié)束。
線程的方法
interrupt():對線程進行中斷操作,將會設(shè)置線程的中斷狀態(tài)為true,至于中斷后線程是死亡,還是等待,還是繼續(xù)運行,取決于線程本身。線程會時不時的檢測這個中斷標志位,以判斷線程是否應(yīng)該中斷。更確切地說,如果線程被wait,join和sleep三種方法之一阻塞,此時調(diào)用interrupt,那么該線程將拋出一個InterrputedException中斷異常,從而提早地終結(jié)被阻塞狀態(tài)。如果線程沒有被阻塞,這時調(diào)用interrupt不會起作用,直到執(zhí)行wait,join,sleep時才馬上拋出InterrputedException。
Thread.interrupted():靜態(tài)方法,內(nèi)部實現(xiàn)是調(diào)用isInterrupted(),并且會重置當前線程的中斷狀態(tài)。
isInterrupted():實例方法,不會重置當前線程的中斷狀態(tài)。作用與Thread.interrupted()相同,獲取調(diào)用線程的中斷狀態(tài)。
yield():靜態(tài)方法,使當前線程讓出CPU占有權(quán),但讓出的時間是不可設(shè)定的,也不會釋放鎖資源。所有執(zhí)行yield()的線程有可能在進入到可執(zhí)行態(tài)后馬上又被執(zhí)行。
join():實例方法,把指定線程加入到當前線程,可以將兩個交替執(zhí)行的線程合并為順序執(zhí)行的線程。比如線程A中調(diào)用了線程B的join(),直到線程B執(zhí)行完后才會繼續(xù)執(zhí)行線程A。
線程的生命周期

線程的狀態(tài):
初始(NEW):新創(chuàng)建了一個線程對象,還沒有調(diào)用start()。
運行(RUNNABLE):Java線程中將就緒(READY)和運行中(RUNNING)兩種狀態(tài)統(tǒng)稱為運行。就緒中的線程在獲取到CPU時間片后進入運行中。
阻塞(BLOCKED):表示線程阻塞于鎖。
等待(WATING):進入該狀態(tài)的線程需要等待其他線程做出一些特定動作(通知或中斷)。
超時等待(TIMED_WATING):該狀態(tài)不同于WATING,它可以在指定時間后自行返回。
終止(TERMINATED):表示該線程已經(jīng)執(zhí)行完畢。
典型線程問題
-
死鎖
兩個或兩個以上的線程在執(zhí)行過程中,由于競爭資源或者由于彼此通信而造成的一種阻塞現(xiàn)象,若無外力作用,它們都將無法推進下去。此時稱系統(tǒng)處于死鎖狀態(tài)或者說產(chǎn)生了死鎖。
死鎖發(fā)生的條件:
1). 互斥條件:指線程對所分配的資源進行排他性使用,即在一段時間內(nèi)某資源只由一個線程占用,如果此時還有其他線程請求該資源,則請求者只能等待,直至占有該資源的線程釋放資源。
2). 請求和保持條件:指線程已經(jīng)至少保持一個資源,但又提出了新的資源請求,而該資源已經(jīng)被其他線程占有,此時請求線程阻塞,但又對自己已獲得的其他資源保持不釋放。
3). 不剝奪條件:指線程已獲得資源,在未使用完之前,不能被剝奪,只能在使用完后由自己釋放。
4). 環(huán)路等待條件:指在發(fā)生死鎖時,必然存在一個線程與資源的環(huán)形鏈,即線程P0正在等待P1占用的資源,P1正在等待P2占用的資源,P2正在等待Pn占用的資源,而Pn則正在等待P1占用的資源。
解決死鎖的方案:
1). 內(nèi)部通過順序比較,確定拿鎖的順序。
2). 采用嘗試拿鎖的機制。
-
活鎖
兩個線程在嘗試拿鎖的機制中,發(fā)生多個線程之間互相謙讓,同一個線程總是拿到同一把鎖,當這個線程繼續(xù)往下執(zhí)行想要拿到另一把鎖時,卻因為拿不到而釋放第一把鎖,下次還是這個線程拿到了第一把鎖,卻又不能往下執(zhí)行,如此周而復(fù)始,導(dǎo)致線程并未阻塞,但是兩個線程都不會往下執(zhí)行了。
解決辦法:每個線程休眠隨機的時間,錯開拿鎖時間。
-
線程饑餓
低優(yōu)先級的線程總是拿不到CPU執(zhí)行時間。