1、進(jìn)程和線程的概念
1)進(jìn)程(Process)是計(jì)算機(jī)中的程序關(guān)于某數(shù)據(jù)集合上的一次運(yùn)行活動(dòng),是系統(tǒng)進(jìn)行資源分配和調(diào)度的基本單位,是操作系統(tǒng)結(jié)構(gòu)的基礎(chǔ)。在早期面向進(jìn)程設(shè)計(jì)的計(jì)算機(jī)結(jié)構(gòu)中,進(jìn)程是程序的基本執(zhí)行實(shí)體;在當(dāng)代面向線程設(shè)計(jì)的計(jì)算機(jī)結(jié)構(gòu)中,進(jìn)程是線程的容器。程序是指令、數(shù)據(jù)及其組織形式的描述,進(jìn)程是程序的實(shí)體。
2)線程(Thread),有時(shí)被稱為輕量級(jí)進(jìn)程(Lightweight Process,LWP),是程序執(zhí)行流的最小單元。線程是程序中一個(gè)單一的順序控制流程,代表不同的執(zhí)行路徑。是進(jìn)程內(nèi)一個(gè)相對(duì)獨(dú)立、可調(diào)度的執(zhí)行單元,是系統(tǒng)獨(dú)立調(diào)度和分派CPU的基本單位,也指運(yùn)行中的程序的調(diào)度單位。在單個(gè)程序中同時(shí)運(yùn)行多個(gè)線程完成不同的工作,稱為多線程.
3)關(guān)系:一個(gè)線程可以創(chuàng)建和撤銷另一個(gè)線程;同一個(gè)進(jìn)程中的多個(gè)線程之間可以并發(fā)執(zhí)行.
相對(duì)進(jìn)程而言,線程是一個(gè)更加接近于執(zhí)行體的概念,它可以與同進(jìn)程中的其他線程共享數(shù)據(jù),但擁有自己的??臻g,擁有獨(dú)立的執(zhí)行序列。
4)區(qū)別
進(jìn)程和線程的主要差別在于它們是不同的操作系統(tǒng)資源管理方式。進(jìn)程有獨(dú)立的地址空間,一個(gè)進(jìn)程崩潰后,在保護(hù)模式下不會(huì)對(duì)其它進(jìn)程產(chǎn)生影響,而線程只是一個(gè)進(jìn)程中的不同執(zhí)行路徑。線程有自己的堆棧和局部變量,所以多進(jìn)程的程序要比多線程的程序健壯,但在進(jìn)程切換時(shí),耗費(fèi)資源較大,效率要差一些。但對(duì)于一些要求同時(shí)進(jìn)行并且又要共享某些變量的并發(fā)操作,只能用線程,不能用進(jìn)程。
- 簡(jiǎn)而言之,一個(gè)程序至少有一個(gè)進(jìn)程,一個(gè)進(jìn)程至少有一個(gè)線程.
- 線程的劃分尺度小于進(jìn)程,使得多線程程序的并發(fā)性高。
- 進(jìn)程在執(zhí)行過(guò)程中擁有獨(dú)立的內(nèi)存單元,而多個(gè)線程共享內(nèi)存,從而極大地提高了程序的運(yùn)行效率。
- 線程在執(zhí)行過(guò)程中與進(jìn)程還是有區(qū)別的。每個(gè)獨(dú)立的線程有一個(gè)程序運(yùn)行的入口、順序執(zhí)行序列和程序的出口。但是線程不能夠獨(dú)立執(zhí)行,必須依存在應(yīng)用程序中,由應(yīng)用程序提供多個(gè)線程執(zhí)行控制。
- 從邏輯角度來(lái)看,多線程的意義在于一個(gè)應(yīng)用程序中,有多個(gè)執(zhí)行部分可以同時(shí)執(zhí)行。但操作系統(tǒng)并沒有將多個(gè)線程看做多個(gè)獨(dú)立的應(yīng)用,來(lái)實(shí)現(xiàn)進(jìn)程的調(diào)度和管理以及資源分配。這就是進(jìn)程和線程的重要區(qū)別。
2、多線程
多線程就是指一個(gè)進(jìn)程中同時(shí)有多個(gè)執(zhí)行路徑(線程)正在執(zhí)行,完成不同的工作,比如卡密充值,當(dāng)你提交卡密之后,都會(huì)先提示提交,等待充值結(jié)果,這種都是把卡密入庫(kù),然后異步開啟多個(gè)線程處理校驗(yàn)和充值操作。
注意:多線程是異步的,但這不代表多線程真的是幾個(gè)線程是在同時(shí)進(jìn)行(在同一個(gè)時(shí)間點(diǎn)上,CPU只能支持一個(gè)線程在執(zhí)行),實(shí)際上是CPU不斷地在各個(gè)線程之間來(lái)回的切換(因?yàn)镃PU切換的速度非常的快,所以給我們?cè)谕瑫r(shí)運(yùn)行的錯(cuò)覺)。
- 為什么要是用多線程?
1.在一個(gè)程序中,有很多的操作是非常耗時(shí)的,如數(shù)據(jù)庫(kù)讀寫操作,IO操作等,如果使用單線程,那么程序就必須等待這些操作執(zhí)行完成之后才能執(zhí)行其他操作。使用多線程,可以在將耗時(shí)任務(wù)放在后臺(tái)繼續(xù)執(zhí)行的同時(shí),同時(shí)執(zhí)行其他操作。
2.可以充分利用CPU的資源,提高程序的效率。
- 線程與高并發(fā)
高并發(fā):高并發(fā)指的是是一種系統(tǒng)運(yùn)行過(guò)程中遇到的一種“短時(shí)間內(nèi)遇到大量操作請(qǐng)求”的情況,主要發(fā)生在web系統(tǒng)集中大量訪問(wèn)或者socket端口集中性收到大量請(qǐng)求(例如:12306的搶票情況;天貓雙十一活動(dòng))。該情況的發(fā)生會(huì)導(dǎo)致系統(tǒng)在這段時(shí)間內(nèi)執(zhí)行大量操作,例如對(duì)資源的請(qǐng)求,數(shù)據(jù)庫(kù)的操作等。如果高并發(fā)處理不好,不僅僅降低了用戶的體驗(yàn)度(請(qǐng)求響應(yīng)時(shí)間過(guò)長(zhǎng)),同時(shí)可能導(dǎo)致系統(tǒng)宕機(jī),嚴(yán)重的甚至導(dǎo)致OOM(out of memory)異常,系統(tǒng)停止工作等。如果要想系統(tǒng)能夠適應(yīng)高并發(fā)狀態(tài),則需要從各個(gè)方面進(jìn)行系統(tǒng)優(yōu)化,包括,硬件、網(wǎng)絡(luò)、系統(tǒng)架構(gòu)、開發(fā)語(yǔ)言的選取、數(shù)據(jù)結(jié)構(gòu)的運(yùn)用、算法優(yōu)化、數(shù)據(jù)庫(kù)優(yōu)化……。
多線程只是在同/異步角度上解決高并發(fā)問(wèn)題的其中的一個(gè)方法手段,是在同一時(shí)刻利用計(jì)算機(jī)閑置資源的一種方式。
多線程在高并發(fā)問(wèn)題中的作用就是充分利用計(jì)算機(jī)資源,使計(jì)算機(jī)的資源在每一時(shí)刻都能達(dá)到最大的利用率,不至于浪費(fèi)計(jì)算機(jī)資源使其閑置。
3、主線程
在JAVA里面,JAVA的線程是通過(guò)java.lang.Thread類來(lái)實(shí)現(xiàn)的,每一個(gè)Thread對(duì)象代表一個(gè)新的線程。實(shí)現(xiàn)多線程編程。
-
程序開發(fā)好了之后,如何運(yùn)行?
1)程序的執(zhí)行過(guò)程都是這樣的:首先把程序的代碼放到內(nèi)存的代碼區(qū)里面,代碼放到代碼區(qū)后并沒有馬上開始執(zhí)行,但這時(shí)候說(shuō)明了一個(gè)進(jìn)程準(zhǔn)備開始,進(jìn)程已經(jīng)產(chǎn)生了,但還沒有開始執(zhí)行,這就是進(jìn)程,所以進(jìn)程其實(shí)是一個(gè)靜態(tài)的概念,它本身就不能動(dòng)。平常所說(shuō)的進(jìn)程的執(zhí)行指的是進(jìn)程里面主線程開始執(zhí)行了。
2)主線程作為程序的入口,也就是main方法(java中main方法是特殊的存在,有main方法,虛擬機(jī)才能調(diào)用并執(zhí)行)。
3)有了主線程,才能通過(guò)主線程來(lái)產(chǎn)生其它的子線程,并且線程最后都要執(zhí)行完成,并釋放。
main方法,里面調(diào)用別的方法,別的方法又調(diào)用其它方法,這個(gè)時(shí)候必須等調(diào)用的所有的方法執(zhí)行結(jié)束,返回main方法,這個(gè)時(shí)候才能繼續(xù)向下執(zhí)行,這種就是簡(jiǎn)單的單線程。
4、線程的創(chuàng)建和啟動(dòng)
使用線程的步驟:定義線程->創(chuàng)建線程對(duì)象->啟動(dòng)線程->終止線程
創(chuàng)建線程三種方式:
1、繼承java.lang.Thread類
2、實(shí)現(xiàn)java.lang.Runable接口
3、實(shí)現(xiàn)java.util.concurrent.Callable接口
使用方式1:
1)自定義線程類MyThread繼承Thread類
2)重寫run()方法,編寫線程執(zhí)行體,自動(dòng)調(diào)用,不能手動(dòng)調(diào)用,否則是普通方法
3)創(chuàng)建線程對(duì)象,調(diào)用start()方法啟動(dòng)線程,一個(gè)線程只能調(diào)用一次start方法,多次啟動(dòng)會(huì)報(bào)錯(cuò):java.lang.IllegalThreadStateException 線程狀態(tài)非法異常
- 示例
public class MyThread extends Thread {
public void run() {
System.out.println("Mythread is running...");
for (int i = 0; i < 10; i++) {
System.out.println("當(dāng)前線程名稱:" + Thread.currentThread().getName() + "----" + i);
}
}
}
public static void main(String[] args) {
// 創(chuàng)建自定義線程對(duì)象
MyThread myThread1 = new MyThread();
// 啟動(dòng)一個(gè)線程,使用的是start方法,自動(dòng)調(diào)用線程的run方法
myThread1.start();
// 直接調(diào)用run方法,就是普通的方法調(diào)用,是順序執(zhí)行的
// myThread.run();
// myThread.start(); 這個(gè)時(shí)候異常,不會(huì)重新再啟動(dòng)線程
}
注意:
1)一旦線程啟動(dòng)后 start 方法就會(huì)立即返回,而不會(huì)等待到 run 方法執(zhí)行完畢才返回
2)多個(gè)線程交替執(zhí)行,不是真正的“并行”,而是我們說(shuō)的并發(fā)(通過(guò)cpu調(diào)度算法,讓用戶看上去同時(shí)執(zhí)行,實(shí)際上從cpu操作層面不是真正的同時(shí)),線程每次執(zhí)行時(shí)長(zhǎng)由分配的CPU時(shí)間片長(zhǎng)度決定
3)直接調(diào)用run方法,這個(gè)變成普通的方法調(diào)用,執(zhí)行調(diào)用的線程是main方法這個(gè)主線程,而且調(diào)用執(zhí)行也是按照順序執(zhí)行的,即單線程。而start方法是單獨(dú)啟動(dòng)一個(gè)子線程,自動(dòng)調(diào)用run方法,及多條執(zhí)行路徑,主線程和子線程并行交替(并發(fā))執(zhí)行。
使用方式二:
1)定義自定義類MyRunnable實(shí)現(xiàn)Runnable接口
2)實(shí)現(xiàn)run()方法,編寫線程執(zhí)行體
3)創(chuàng)建線程對(duì)象,同樣調(diào)用start()方法啟動(dòng)線程
- 示例
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("MyRunnable is running...");
for (int i = 0; i < 10; i++) {
System.out.println("當(dāng)前線程名稱:" + Thread.currentThread().getName() + "----" + i);
}
}
}
public static void main(String[] args) {
MyRunnable runnable = new MyRunnable();
// 以形參的方式傳遞給Thread類,構(gòu)造線程實(shí)例
Thread thread1 = new Thread(myRunnable);
thread1.start();
}
兩種方式對(duì)比:
實(shí)現(xiàn)Runnable接口相對(duì)于繼承Thread類來(lái)說(shuō),顯著的好處:
(1)適合多個(gè)相同程序代碼的線程去處理同一資源的情況,把虛擬CPU(線程)同程序的代碼,數(shù)據(jù)有效的分離,較好地體現(xiàn)了面向?qū)ο蟮脑O(shè)計(jì)思想。
(2)可以避免由于Java的單繼承特性帶來(lái)的局限。我們經(jīng)常碰到這樣一種情況,即當(dāng)我們要將已經(jīng)繼承了某一個(gè)類的子類放入多線程中,由于一個(gè)類不能同時(shí)有兩個(gè)父類,所以不能用繼承Thread類的方式,那么,這個(gè)類就只能采用實(shí)現(xiàn)Runnable接口的方式了。
(3)有利于程序的健壯性,代碼能夠被多個(gè)線程共享,代碼與數(shù)據(jù)是獨(dú)立的。當(dāng)多個(gè)線程的執(zhí)行代碼來(lái)自同一個(gè)類的實(shí)例時(shí),即稱它們共享相同的代碼。 多個(gè)線程操作相同的數(shù)據(jù),與它們的代碼無(wú)關(guān)。當(dāng)共享訪問(wèn)相同的對(duì)象是,即它們共享相同的數(shù)據(jù)。當(dāng)線程被構(gòu)造時(shí),需要的代碼和數(shù)據(jù)通過(guò)一個(gè)對(duì)象作為構(gòu)造函數(shù) 實(shí)參傳遞進(jìn)去,這個(gè)對(duì)象就是一個(gè)實(shí)現(xiàn)了Runnable接口的類的實(shí)例。
使用方式3
1)定義自定義類MyCallable實(shí)現(xiàn)Callable接口
2)實(shí)現(xiàn)call()方法,編寫線程執(zhí)行體
3)創(chuàng)建線程對(duì)象,同樣調(diào)用start()方法啟動(dòng)線程
- 示例
/**
* 實(shí)現(xiàn)線程的方式3:實(shí)現(xiàn)Callable接口,可以獲取返回值
*/
public class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("開始運(yùn)算:");
int sum = 0;
for (int i = 1; i < 10; i++) {
sum += i;
}
return sum;
}
public static void main(String[] args) {
MyCallable myCallable = new MyCallable();
// 執(zhí)行,必須借助實(shí)現(xiàn)類:FutureTask,接收返回結(jié)果
FutureTask<Integer> result = new FutureTask<Integer>(myCallable);
System.out.println("執(zhí)行開始:");
// 創(chuàng)建線程并啟動(dòng)
new Thread(result).start();
// 獲取結(jié)果
Integer sumResult;
try {
sumResult = result.get();
System.out.println("10以內(nèi)數(shù)字之和:" + sumResult);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.println("執(zhí)行結(jié)束?。?!");
}
}
- 小結(jié)
1、實(shí)現(xiàn)Callable接口和實(shí)現(xiàn)Runnable接口都可以實(shí)現(xiàn)多線程,但是實(shí)現(xiàn)Runnable接口的run()方法是沒有返回值的,而Callable接口可以返回指定類型的值。
2、Callable接口在java.util.consurrent包中,JDK 1.5之后提出。
3、Callable接口本身并不具備執(zhí)行能力,因此Java提供了一個(gè)FutureTask類來(lái)使用Callable接口定義的具有返回值的任務(wù)。
4、Callable的對(duì)象必須先封裝到FutureTask中,然后通過(guò)FutureTask傳給Thread類,這樣才能啟動(dòng)多線程。
5、FutureTask的get()方法可以獲取到Callable接口實(shí)現(xiàn)任務(wù)的返回值。
6、call()方法可拋出異常,而run()方法是不能拋出異常的。