一诅妹、進(jìn)程和線程的區(qū)別:
進(jìn)程:每個(gè)進(jìn)程都有獨(dú)立的代碼和數(shù)據(jù)空間(進(jìn)程上下文),進(jìn)程間的切換會(huì)有較大的開(kāi)銷毅人,一個(gè)進(jìn)程包含1--n個(gè)線程吭狡。(進(jìn)程是資源分配的最小單位)
線程:同一類線程共享代碼和數(shù)據(jù)空間,每個(gè)線程有獨(dú)立的運(yùn)行棧和程序計(jì)數(shù)器(PC)丈莺,線程切換開(kāi)銷小划煮。(線程是cpu調(diào)度的最小單位)線程和進(jìn)程一樣分為五個(gè)階段:創(chuàng)建、就緒缔俄、運(yùn)行弛秋、阻塞、終止俐载。
多進(jìn)程是指操作系統(tǒng)能同時(shí)運(yùn)行多個(gè)任務(wù)(程序)蟹略。
多線程是指在同一程序中有多個(gè)順序流在執(zhí)行。
在java中要想實(shí)現(xiàn)多線程遏佣,有兩種手段挖炬,一種是繼續(xù)Thread類,另外一種是實(shí)現(xiàn)Runable接口.
二状婶、Thread和Runnable的區(qū)別:
如果一個(gè)類繼承Thread意敛,則不適合資源共享馅巷。但是如果實(shí)現(xiàn)了Runable接口的話,則很容易的實(shí)現(xiàn)資源共享草姻。
繼承Thread類創(chuàng)建線程示例:
// 通過(guò)繼承Thread類來(lái)創(chuàng)建線程類
public class MyThreadTest extends Thread {
private int i;
// 重寫(xiě)run方法令杈,run方法的方法體就是線程執(zhí)行體
public void run() {
for (; i < 100; i++) {
// 當(dāng)線程類繼承Thread類時(shí),直接使用this即可獲取當(dāng)前線程
// Thread對(duì)象的getName()返回當(dāng)前該線程的名字
// 因此可以直接調(diào)用getName()方法返回當(dāng)前線程的名
System.out.println(getName() + "" + i);
}
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
// 調(diào)用Thread的currentThread方法獲取當(dāng)前線程
System.out.println(Thread.currentThread().getName() + "" + i);
if (i == 20) {
// 創(chuàng)建碴倾、并啟動(dòng)第一條線程
new MyThreadTest().start();
// 創(chuàng)建逗噩、并啟動(dòng)第二條線程
new MyThreadTest().start();
}
}
}
}
部分運(yùn)行結(jié)果:
雖然上面程序只顯式地創(chuàng)建并啟動(dòng)了2個(gè)線程,但實(shí)際上程序有3個(gè)線程跌榔,即程序顯式創(chuàng)建的2個(gè)子線程和1個(gè)主線程异雁。前面已經(jīng)提到,當(dāng)Java程序開(kāi)始運(yùn)行后僧须,程序至少會(huì)創(chuàng)建一個(gè)主線程纲刀,主線程的線程執(zhí)行體不是由run()方法確定的,而是由main()方法確定的担平,main()方法的方法體代表主線程的線程執(zhí)行體示绊。
該程序無(wú)論被執(zhí)行多少次輸出的記錄數(shù)是一定的,一共是300條記錄暂论。主線程會(huì)執(zhí)行for循環(huán)打印100條記錄面褐,兩個(gè)子線程分別打印100條記錄,一共300條記錄取胎。因?yàn)閕變量是MyThreadTest的實(shí)例屬性展哭,而不是局部變量,但因?yàn)槌绦蛎看蝿?chuàng)建線程對(duì)象時(shí)都需要?jiǎng)?chuàng)建一個(gè)MyThreadTest對(duì)象闻蛀,所以Thread-0和Thread-1不能共享該實(shí)例屬性匪傍,所以每個(gè)線程都將執(zhí)行100次循環(huán)。
實(shí)現(xiàn)Runnable接口創(chuàng)建線程示例:
public class MyRunnableTest implements Runnable {
private int i;
void print(){
System.out.println(Thread.currentThread().getName() + "" + i);
}
// run方法同樣是線程執(zhí)行體
public void run() {
for (; i < 100; i++) {
// 當(dāng)線程類實(shí)現(xiàn)Runnable接口時(shí)觉痛,
// 如果想獲取當(dāng)前線程役衡,只能用Thread.currentThread()方法。
print();
}
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "" + i);
if (i == 20) {
MyRunnableTest st = new MyRunnableTest();
// 通過(guò)new Thread(target , name)方法創(chuàng)建新線程
new Thread(st, "新線程-1").start();
new Thread(st, "新線程-2").start();
}
}
}
}
結(jié)果:
實(shí)現(xiàn)Runnable接口比繼承Thread類所具有的優(yōu)勢(shì):
1):適合多個(gè)相同的程序代碼的線程去處理同一個(gè)資源
2):可以避免java中的單繼承的限制
3):增加程序的健壯性薪棒,代碼可以被多個(gè)線程共享手蝎,代碼和數(shù)據(jù)獨(dú)立
4):線程池只能放入實(shí)現(xiàn)Runable或callable類線程,不能直接放入繼承Thread的類
在java中盗尸,每次程序運(yùn)行至少啟動(dòng)2個(gè)線程柑船。一個(gè)是main線程帽撑,一個(gè)是垃圾收集線程泼各。因?yàn)槊慨?dāng)使用java命令執(zhí)行一個(gè)類的時(shí)候,實(shí)際上都會(huì)啟動(dòng)一個(gè)JVM亏拉,每一個(gè)JVM實(shí)際上就是在操作系統(tǒng)中啟動(dòng)了一個(gè)進(jìn)程扣蜻。
三逆巍、線程生命周期:
1、新建狀態(tài)(New):新創(chuàng)建了一個(gè)線程對(duì)象莽使。
2锐极、就緒狀態(tài)(Runnable):線程對(duì)象創(chuàng)建后,其他線程調(diào)用了該對(duì)象的start()方法芳肌。該狀態(tài)的線程位于可運(yùn)行線程池中灵再,變得可運(yùn)行,等待獲取CPU的使用權(quán)亿笤。
3翎迁、運(yùn)行狀態(tài)(Running):就緒狀態(tài)的線程獲取了CPU,執(zhí)行程序代碼净薛。
4汪榔、阻塞狀態(tài)(Blocked):阻塞狀態(tài)是線程因?yàn)槟撤N原因放棄CPU使用權(quán),暫時(shí)停止運(yùn)行肃拜。直到線程進(jìn)入就緒狀態(tài)痴腌,才有機(jī)會(huì)轉(zhuǎn)到運(yùn)行狀態(tài)。阻塞的情況分三種:
(一)燃领、等待阻塞:運(yùn)行的線程執(zhí)行wait()方法士聪,JVM會(huì)把該線程放入等待池中。(wait會(huì)釋放持有的鎖)
(二)猛蔽、同步阻塞:運(yùn)行的線程在獲取對(duì)象的同步鎖時(shí)戚嗅,若該同步鎖被別的線程占用,則JVM會(huì)把該線程放入鎖池中枢舶。
(三)懦胞、其他阻塞:運(yùn)行的線程執(zhí)行sleep()或join()方法,或者發(fā)出了I/O請(qǐng)求時(shí)凉泄,JVM會(huì)把該線程置為阻塞狀態(tài)躏尉。當(dāng)sleep()狀態(tài)超時(shí)、join()等待線程終止或者超時(shí)后众、或者I/O處理完畢時(shí)胀糜,線程重新轉(zhuǎn)入就緒狀態(tài)。(注意,sleep是不會(huì)釋放持有的鎖)
5蒂誉、死亡狀態(tài)(Dead):線程執(zhí)行完了或者因異常退出了run()方法教藻,該線程結(jié)束生命周期。
線程會(huì)以如下3種方式結(jié)束右锨,結(jié)束后就處于死亡狀態(tài):
① run()或call()方法執(zhí)行完成括堤,線程正常結(jié)束。
② 線程拋出一個(gè)未捕獲的Exception或Error。
③ 直接調(diào)用該線程stop()方法來(lái)結(jié)束該線程——該方法容易導(dǎo)致死鎖悄窃,通常不推薦使用
四讥电、常用函數(shù)說(shuō)明:
①sleep(long millis): 線程睡眠。
使線程轉(zhuǎn)到阻塞狀態(tài)轧抗。millis參數(shù)設(shè)定睡眠的時(shí)間恩敌,以毫秒為單位。當(dāng)睡眠結(jié)束后横媚,就轉(zhuǎn)為就緒(Runnable)狀態(tài)纠炮。
②join():線程加入。
join是Thread類的一個(gè)方法灯蝴,啟動(dòng)線程后直接調(diào)用抗碰,即join()的作用是:“等待該線程終止”,這里需要理解的就是該線程是指的主線程等待子線程的終止绽乔。也就是在子線程調(diào)用了join()方法后面的代碼弧蝇,只有等到子線程結(jié)束了才能執(zhí)行。如果有多個(gè)子線程都調(diào)用了join()方法折砸,那么主線程需要等待多個(gè)子線程結(jié)束才能繼續(xù)執(zhí)行join()方法后的代碼看疗。
③yield():線程讓步。暫停當(dāng)前正在執(zhí)行的線程對(duì)象睦授,把執(zhí)行機(jī)會(huì)讓給相同或者更高優(yōu)先級(jí)的線程两芳。
yield()應(yīng)該做的是讓當(dāng)前運(yùn)行線程回到可運(yùn)行狀態(tài),以允許具有相同優(yōu)先級(jí)的其他線程獲得運(yùn)行機(jī)會(huì)去枷。因此怖辆,使用yield()的目的是讓相同優(yōu)先級(jí)的線程之間能適當(dāng)?shù)妮嗈D(zhuǎn)執(zhí)行。但是删顶,實(shí)際中無(wú)法保證yield()達(dá)到讓步目的竖螃,因?yàn)樽尣降木€程還有可能被線程調(diào)度程序再次選中。
結(jié)論:yield()從未導(dǎo)致線程轉(zhuǎn)到等待/睡眠/阻塞狀態(tài)逗余。在大多數(shù)情況下特咆,yield()將導(dǎo)致線程從運(yùn)行狀態(tài)轉(zhuǎn)到可運(yùn)行狀態(tài),但有可能沒(méi)有效果录粱。
sleep()和yield()的區(qū)別:
sleep()和yield()的區(qū)別):sleep()使當(dāng)前線程進(jìn)入停滯狀態(tài)腻格,所以執(zhí)行sleep()的線程在指定的時(shí)間內(nèi)肯定不會(huì)被執(zhí)行;yield()只是使當(dāng)前線程重新回到可執(zhí)行狀態(tài)啥繁,所以執(zhí)行yield()的線程有可能在進(jìn)入到可執(zhí)行狀態(tài)后馬上又被執(zhí)行菜职。
sleep 方法使當(dāng)前運(yùn)行中的線程睡眼一段時(shí)間,進(jìn)入不可運(yùn)行狀態(tài)旗闽,這段時(shí)間的長(zhǎng)短是由程序設(shè)定的酬核,yield 方法使當(dāng)前線程讓出 CPU 占有權(quán)蜜另,但讓出的時(shí)間是不可設(shè)定的。實(shí)際上愁茁,yield()方法對(duì)應(yīng)了如下操作:先檢測(cè)當(dāng)前是否有相同優(yōu)先級(jí)的線程處于同可運(yùn)行狀態(tài)蚕钦,如有亭病,則把 CPU 的占有權(quán)交給此線程鹅很,否則,繼續(xù)運(yùn)行原來(lái)的線程罪帖。所以yield()方法稱為“退讓”促煮,它把運(yùn)行機(jī)會(huì)讓給了同等優(yōu)先級(jí)的其他線程
另外,sleep 方法允許較低優(yōu)先級(jí)的線程獲得運(yùn)行機(jī)會(huì)整袁,但 yield() 方法執(zhí)行時(shí)菠齿,當(dāng)前線程仍處在可運(yùn)行狀態(tài),所以坐昙,不可能讓出較低優(yōu)先級(jí)的線程些時(shí)獲得 CPU 占有權(quán)绳匀。在一個(gè)運(yùn)行系統(tǒng)中,如果較高優(yōu)先級(jí)的線程沒(méi)有調(diào)用 sleep 方法炸客,又沒(méi)有受到 IO 阻塞疾棵,那么,較低優(yōu)先級(jí)線程只能等待所有較高優(yōu)先級(jí)的線程運(yùn)行結(jié)束痹仙,才有機(jī)會(huì)運(yùn)行是尔。
④setPriority(): 更改線程的優(yōu)先級(jí)。
⑤interrupt():不要以為它是中斷某個(gè)線程开仰!它只是線線程發(fā)送一個(gè)中斷信號(hào)拟枚,讓線程在無(wú)限等待時(shí)(如死鎖時(shí))能拋出異常,從而結(jié)束線程众弓,但是如果你吃掉了這個(gè)異常恩溅,那么這個(gè)線程還是不會(huì)中斷的!
⑥wait()
Obj.wait()谓娃,與Obj.notify()必須要與synchronized(Obj)一起使用暴匠,也就是wait,與notify是針對(duì)已經(jīng)獲取了Obj鎖進(jìn)行操作,從語(yǔ)法角度來(lái)說(shuō)就是Obj.wait(),Obj.notify必須在synchronized(Obj){...}語(yǔ)句塊內(nèi)傻粘。從功能上來(lái)說(shuō)wait就是說(shuō)線程在獲取對(duì)象鎖后每窖,主動(dòng)釋放對(duì)象鎖,同時(shí)本線程休眠弦悉。直到有其它線程調(diào)用對(duì)象的notify()喚醒該線程窒典,才能繼續(xù)獲取對(duì)象鎖,并繼續(xù)執(zhí)行稽莉。相應(yīng)的notify()就是對(duì)對(duì)象鎖的喚醒操作瀑志。但有一點(diǎn)需要注意的是notify()調(diào)用后,并不是馬上就釋放對(duì)象鎖的,而是在相應(yīng)的synchronized(){}語(yǔ)句塊執(zhí)行結(jié)束劈猪,自動(dòng)釋放鎖后昧甘,JVM會(huì)在wait()對(duì)象鎖的線程中隨機(jī)選取一線程,賦予其對(duì)象鎖战得,喚醒線程充边,繼續(xù)執(zhí)行。這樣就提供了在線程間同步常侦、喚醒的操作浇冰。Thread.sleep()與Object.wait()二者都可以暫停當(dāng)前線程,釋放CPU控制權(quán)聋亡,主要的區(qū)別在于Object.wait()在釋放CPU同時(shí)肘习,釋放了對(duì)象鎖的控制。
wait和sleep區(qū)別:
共同點(diǎn):
他們都是在多線程的環(huán)境下坡倔,都可以在程序的調(diào)用處阻塞指定的毫秒數(shù)漂佩,并返回。
wait()和sleep()都可以通過(guò)interrupt()方法 打斷線程的暫停狀態(tài) 罪塔,從而使線程立刻拋出InterruptedException投蝉。
如果線程A希望立即結(jié)束線程B,則可以對(duì)線程B對(duì)應(yīng)的Thread實(shí)例調(diào)用interrupt方法垢袱。如果此刻線程B正在wait/sleep /join墓拜,則線程B會(huì)立刻拋出InterruptedException,在catch() {} 中直接return即可安全地結(jié)束線程请契。
需要注意的是咳榜,InterruptedException是線程自己從內(nèi)部拋出的,并不是interrupt()方法拋出的爽锥。對(duì)某一線程調(diào)用 interrupt()時(shí)涌韩,如果該線程正在執(zhí)行普通的代碼,那么該線程根本就不會(huì)拋出InterruptedException氯夷。但是臣樱,一旦該線程進(jìn)入到 wait()/sleep()/join()后,就會(huì)立刻拋出InterruptedException 腮考。
不同點(diǎn):
- Thread類的方法:sleep(),yield()等
Object的方法:wait()和notify()等
- 每個(gè)對(duì)象都有一個(gè)鎖來(lái)控制同步訪問(wèn)雇毫。synchronized關(guān)鍵字可以和對(duì)象的鎖交互,來(lái)實(shí)現(xiàn)線程的同步踩蔚。
sleep方法沒(méi)有釋放鎖棚放,而wait方法釋放了鎖,使得其他線程可以使用同步控制塊或者方法馅闽。
- wait飘蚯,notify和notifyAll只能在同步控制方法或者同步控制塊里面使用馍迄,而sleep可以在任何地方使用
所以sleep()和wait()方法的最大區(qū)別是:
sleep()睡眠時(shí),保持對(duì)象鎖局骤,仍然占有該鎖攀圈;而wait()睡眠時(shí),釋放對(duì)象鎖峦甩。
但是wait()和sleep()都可以通過(guò)interrupt()方法打斷線程的暫停狀態(tài)赘来,從而使線程立刻拋出InterruptedException(但不建議使用該方法)。
sleep()方法
sleep()使當(dāng)前線程進(jìn)入停滯狀態(tài)(阻塞當(dāng)前線程)穴店,讓出CUP的使用撕捍、目的是不讓當(dāng)前線程獨(dú)自霸占該進(jìn)程所獲的CPU資源拿穴,以留一定時(shí)間給其他線程執(zhí)行的機(jī)會(huì);
sleep()是Thread類的Static(靜態(tài))的方法泣洞;因此他不能改變對(duì)象的鎖,所以當(dāng)在一個(gè)synchronized塊中調(diào)用sleep()方法時(shí)默色,線程雖然休眠了球凰,但是對(duì)象的鎖并木有被釋放,其他線程無(wú)法訪問(wèn)這個(gè)對(duì)象(即使睡著也持有對(duì)象鎖)腿宰。
在sleep()休眠時(shí)間期滿后呕诉,該線程不一定會(huì)立即執(zhí)行,這是因?yàn)槠渌€程可能正在運(yùn)行而且沒(méi)有被調(diào)度為放棄執(zhí)行吃度,除非此線程具有更高的優(yōu)先級(jí)甩挫。
wait()方法
wait()方法是Object類里的方法;當(dāng)一個(gè)線程執(zhí)行到wait()方法時(shí)椿每,它就進(jìn)入到一個(gè)和該對(duì)象相關(guān)的等待池中伊者,同時(shí)失去(釋放)了對(duì)象的鎖(暫時(shí)失去鎖,wait(long timeout)超時(shí)時(shí)間到后還需要返還對(duì)象鎖)间护;其他線程可以訪問(wèn)亦渗;
wait()使用notify或者notifyAll或者指定睡眠時(shí)間來(lái)喚醒當(dāng)前等待池中的線程。
wiat()必須放在synchronized block中汁尺,否則會(huì)在program runtime時(shí)扔出”java.lang.IllegalMonitorStateException“異常法精。
五、synchronized關(guān)鍵字:
1痴突、synchronized關(guān)鍵字的作用域有二種:
1)是某個(gè)對(duì)象實(shí)例內(nèi)搂蜓,synchronized aMethod(){}可以防止多個(gè)線程同時(shí)訪問(wèn)這個(gè)對(duì)象的synchronized方法(如果一個(gè)對(duì)象有多個(gè)synchronized方法,只要一個(gè)線程訪問(wèn)了其中的一個(gè)synchronized方法辽装,其它線程不能同時(shí)訪問(wèn)這個(gè)對(duì)象中任何一個(gè)synchronized方法)帮碰。這時(shí),不同的對(duì)象實(shí)例的synchronized方法是不相干擾的如迟。也就是說(shuō)收毫,其它線程照樣可以同時(shí)訪問(wèn)相同類的另一個(gè)對(duì)象實(shí)例中的synchronized方法攻走;
2)是某個(gè)類的范圍,synchronized static aStaticMethod{}防止多個(gè)線程同時(shí)訪問(wèn)這個(gè)類中的synchronized static 方法此再。它可以對(duì)類的所有對(duì)象實(shí)例起作用昔搂。
2、除了方法前用synchronized關(guān)鍵字输拇,synchronized關(guān)鍵字還可以用于方法中的某個(gè)區(qū)塊中摘符,表示只對(duì)這個(gè)區(qū)塊的資源實(shí)行互斥訪問(wèn)。用法是: synchronized(this){/區(qū)塊/}策吠,它的作用域是當(dāng)前對(duì)象逛裤; 或者synchronized(class){/區(qū)塊/},它的作用域是這個(gè)類的所有實(shí)例對(duì)象猴抹;
3带族、synchronized關(guān)鍵字是不能繼承的,也就是說(shuō)蟀给,基類的方法synchronized f(){} 在繼承類中并不自動(dòng)是synchronized f(){}蝙砌,而是變成了f(){}。繼承類需要你顯式的指定它的某個(gè)方法為synchronized方法跋理;