多線程并發(fā)知識(shí)庫(kù)
JAVA線程實(shí)現(xiàn)/創(chuàng)建的四種方式
1.繼承Thread類
Thread類本質(zhì)上是實(shí)現(xiàn)了Runnable接口的一個(gè)實(shí)例郑趁,代表一個(gè)線程的實(shí)例。啟動(dòng)線程的唯一方法就是通過Thread類的start()實(shí)例方法风纠。start()方法是一個(gè)native方法夸楣,它將啟動(dòng)一個(gè)新線程,并執(zhí)行run()方法肠牲。
public class MyThread extends Thread {
public void run() {System.out.println("MyThread.run()");
}
}
MyThread myThread1 = new MyThread();
myThread1.start();
2.實(shí)現(xiàn)Runnable接口
如果自己的類已經(jīng)extends另一個(gè)類,就無(wú)法直接extends Thread靴跛,此時(shí)缀雳,可以實(shí)現(xiàn)一個(gè)Runnable接口。
public class MyThread extends OtherClass implements Runnable {
public void run() {
System.out.println("MyThread.run()");
}
}
//啟動(dòng)MyThread梢睛,需要首先實(shí)例化一個(gè)Thread肥印,并傳入自己的MyThread實(shí)例:
MyThread myThread = new MyThread();
Thread thread = new Thread(myThread);
thread.start();
//事實(shí)上,當(dāng)傳入一個(gè)Runnable target參數(shù)給Thread后绝葡,Thread的run()方法就會(huì)調(diào)用target.run() public void run() {
if (target != null) { target.run();
}
}
3.ExecutorService深碱、Callable<Class>、Future有返回值線程
//創(chuàng)建一個(gè)線程池
ExecutorService pool = Executors.newFixedThreadPool(taskSize);
// 創(chuàng)建多個(gè)有返回值的任務(wù)
List<Future> list = new ArrayList<Future>();
for (int i = 0; i < taskSize; i++) {
Callable c = new MyCallable(i + " ");
// 執(zhí)行任務(wù)并獲取Future對(duì)象
Future f = pool.submit(c);
list.add(f);
}
// 關(guān)閉線程池
pool.shutdown();
// 獲取所有并發(fā)任務(wù)的運(yùn)行結(jié)果
for (Future f : list) {
// 從Future對(duì)象上獲取任務(wù)的返回值藏畅,并輸出到控制臺(tái)
System.out.println("res:" + f.get().toString());
}
4.基于線程池的方式
線程和數(shù)據(jù)庫(kù)連接這些資源都是非常寶貴的資源莹痢。那么每次需要的時(shí)候創(chuàng)建上沐,不需要的時(shí)候銷毀荆隘,是非常浪費(fèi)資源的产禾。那么我們就可以使用緩存的策略揉抵,也就是使用線程池侣诺。
// 創(chuàng)建線程池
ExecutorService threadPool = Executors.newFixedThreadPool(10);
while(true) {
threadPool.execute(new Runnable() { // 提交多個(gè)線程任務(wù)祠饺,并執(zhí)行
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " is running ..");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
4種線程池
Java里面線程池的頂級(jí)接口是Executor诅岩,但是嚴(yán)格意義上講Executor并不是一個(gè)線程池声离,而只是一個(gè)執(zhí)行線程的工具章办。真正的線程池接口是ExecutorService锉走。
1.newCachedThreadPool
創(chuàng)建一個(gè)可根據(jù)需要?jiǎng)?chuàng)建新線程的線程池,但是在以前構(gòu)造的線程可用時(shí)將重用它們藕届。對(duì)于執(zhí)行很多短期異步任務(wù)的程序而言挪蹭,這些線程池通常可提高程序性能休偶。調(diào)用 execute 將重用以前構(gòu)造的線程(如果線程可用)梁厉。如果現(xiàn)有線程沒有可用的,則創(chuàng)建一個(gè)新線程并添加到池中。終止并從緩存中移除那些已有 60 秒鐘未被使用的線程词顾。因此八秃,長(zhǎng)時(shí)間保持空閑的線程池不會(huì)使用任何資源。
2.newFixedThreadPool
創(chuàng)建一個(gè)可重用固定線程數(shù)的線程池肉盹,以共享的無(wú)界隊(duì)列方式來(lái)運(yùn)行這些線程昔驱。在任意點(diǎn),在大多數(shù) nThreads 線程會(huì)處于處理任務(wù)的活動(dòng)狀態(tài)上忍。如果在所有線程處于活動(dòng)狀態(tài)時(shí)提交附加任務(wù)骤肛,則在有可用線程之前,附加任務(wù)將在隊(duì)列中等待窍蓝。如果在關(guān)閉前的執(zhí)行期間由于失敗而導(dǎo)致任何線程終止腋颠,那么一個(gè)新線程將代替它執(zhí)行后續(xù)的任務(wù)(如果需要)。在某個(gè)線程被顯式地關(guān)閉之前它抱,池中的線程將一直存在。
3.newScheduledThreadPool
newScheduledThreadPool 創(chuàng)建一個(gè)線程池朴艰,它可安排在給定延遲后運(yùn)行命令或者定期地執(zhí)行观蓄。
ScheduledExecutorService scheduledThreadPool= Executors.newScheduledThreadPool(3); scheduledThreadPool.schedule(newRunnable(){
@Override public void run() {
System.out.println("延遲三秒"); }
}, 3, TimeUnit.SECONDS); scheduledThreadPool.scheduleAtFixedRate(newRunnable(){
@Override public void run() {
System.out.println("延遲1秒后每三秒執(zhí)行一次");
} },1,3,TimeUnit.SECONDS);
4.newSingleThreadExecutor
Executors.newSingleThreadExecutor()返回一個(gè)線程池(這個(gè)線程池只有一個(gè)線程),這個(gè)線程池可以在線程死后(或發(fā)生異常時(shí))重新啟動(dòng)一個(gè)線程來(lái)替代原來(lái)的線程繼續(xù)執(zhí)行下去!
線程生命周期(狀態(tài))
當(dāng)線程被創(chuàng)建并啟動(dòng)以后祠墅,它既不是一啟動(dòng)就進(jìn)入了執(zhí)行狀態(tài)侮穿,也不是一直處于執(zhí)行狀態(tài)。在線程的生命周期中毁嗦,它要經(jīng)過新建(New)亲茅、就緒(Runnable)、運(yùn)行(Running)狗准、阻塞(Blocked)和死亡(Dead)5種狀態(tài)克锣。尤其是當(dāng)線程啟動(dòng)以后,它不可能一直"霸占"著CPU獨(dú)自運(yùn)行腔长,所以CPU需要在多條線程之間切換袭祟,于是線程狀態(tài)也會(huì)多次在運(yùn)行、阻塞之間切換
1. 新建狀態(tài)(NEW)
當(dāng)程序使用new關(guān)鍵字創(chuàng)建了一個(gè)線程之后捞附,該線程就處于新建狀態(tài)巾乳,此時(shí)僅由JVM為其分配內(nèi)存,并初始化其成員變量的值
2.就緒狀態(tài)(RUNNABLE)
當(dāng)線程對(duì)象調(diào)用了start()方法之后鸟召,該線程處于就緒狀態(tài)胆绊。Java虛擬機(jī)會(huì)為其創(chuàng)建方法調(diào)用棧和程序計(jì)數(shù)器,等待調(diào)度運(yùn)行欧募。
3.運(yùn)行狀態(tài)(RUNNING)
如果處于就緒狀態(tài)的線程獲得了CPU压状,開始執(zhí)行run()方法的線程執(zhí)行體,則該線程處于運(yùn)行狀態(tài)跟继。
4.阻塞狀態(tài)(BLOCKED)
阻塞狀態(tài)是指線程因?yàn)槟撤N原因放棄了cpu 使用權(quán)何缓,也即讓出了cpu timeslice肢础,暫時(shí)停止運(yùn)行。直到線程進(jìn)入可運(yùn)行(runnable)狀態(tài)碌廓,才有機(jī)會(huì)再次獲得cpu timeslice 轉(zhuǎn)到運(yùn)行(running)狀態(tài)传轰。阻塞的情況分三種:
等待阻塞(o.wait->等待對(duì)列):
運(yùn)行(running)的線程執(zhí)行o.wait()方法,JVM會(huì)把該線程放入等待隊(duì)列(waitting queue)中谷婆。同步阻塞(lock->鎖池)
運(yùn)行(running)的線程在獲取對(duì)象的同步鎖時(shí)慨蛙,若該同步鎖被別的線程占用,則JVM會(huì)把該線程放入鎖池(lock pool)中纪挎。
其他阻塞(sleep/join)
運(yùn)行(running)的線程執(zhí)行Thread.sleep(long ms)或t.join()方法期贫,或者發(fā)出了I/O請(qǐng)求時(shí),JVM會(huì)把該線程置為阻塞狀態(tài)异袄。當(dāng)sleep()狀態(tài)超時(shí)通砍、join()等待線程終止或者超時(shí)、或者I/O處理完畢時(shí)烤蜕,線程重新轉(zhuǎn)入可運(yùn)行(runnable)狀態(tài)封孙。
5.線程死亡(DEAD)
線程會(huì)以下面三種方式結(jié)束,結(jié)束后就是死亡狀態(tài)讽营。
正常結(jié)束1. run()或call()方法執(zhí)行完成虎忌,線程正常結(jié)束。
異常結(jié)束2. 線程拋出一個(gè)未捕獲的Exception或Error橱鹏。
調(diào)用stop 3. 直接調(diào)用該線程的stop()方法來(lái)結(jié)束該線程—該方法通常容易導(dǎo)致死鎖膜蠢,不推薦使用。
終止線程4種方式
1.正常運(yùn)行結(jié)束
程序運(yùn)行結(jié)束莉兰,線程自動(dòng)結(jié)束挑围。
2.使用退出標(biāo)志退出線程
一般run()方法執(zhí)行完,線程就會(huì)正常結(jié)束糖荒,然而贪惹,常常有些線程是伺服線程。它們需要長(zhǎng)時(shí)間的運(yùn)行寂嘉,只有在外部某些條件滿足的情況下奏瞬,才能關(guān)閉這些線程。使用一個(gè)變量來(lái)控制循環(huán)泉孩,例如:最直接的方法就是設(shè)一個(gè)boolean類型的標(biāo)志硼端,并通過設(shè)置這個(gè)標(biāo)志為true或false來(lái)控制while循環(huán)是否退出,代碼示例:
public class ThreadSafe extends Thread {
public volatile boolean exit = false;
public void run() {
while (!exit){
//do something
}
}
}
定義了一個(gè)退出標(biāo)志exit寓搬,當(dāng)exit為true時(shí)珍昨,while循環(huán)退出,exit的默認(rèn)值為false.在定義exit時(shí),使用了一個(gè)Java關(guān)鍵字volatile镣典,這個(gè)關(guān)鍵字的目的是使exit同步兔毙,也就是說(shuō)在同一時(shí)刻只能由一個(gè)線程來(lái)修改exit的值。
3.Interrupt方法結(jié)束線程
使用interrupt()方法來(lái)中斷線程有兩種情況:
\1. 線程處于阻塞狀態(tài):如使用了sleep,同步鎖的wait,socket中的receiver,accept等方法時(shí)兄春,會(huì)使線程處于阻塞狀態(tài)澎剥。當(dāng)調(diào)用線程的interrupt()方法時(shí),會(huì)拋出InterruptException異常赶舆。阻塞中的那個(gè)方法拋出這個(gè)異常哑姚,通過代碼捕獲該異常,然后break跳出循環(huán)狀態(tài)芜茵,從而讓我們有機(jī)會(huì)結(jié)束這個(gè)線程的執(zhí)行叙量。通常很多人認(rèn)為只要調(diào)用interrupt方法線程就會(huì)結(jié)束,實(shí)際上是錯(cuò)的九串, 一定要先捕獲InterruptedException異常之后通過break來(lái)跳出循環(huán)绞佩,才能正常結(jié)束run方法。
\2. 線程未處于阻塞狀態(tài):使用isInterrupted()判斷線程的中斷標(biāo)志來(lái)退出循環(huán)猪钮。當(dāng)使用interrupt()方法時(shí)品山,中斷標(biāo)志就會(huì)置true,和使用自定義的標(biāo)志來(lái)控制循環(huán)是一樣的道理躬贡。
public class ThreadSafe extends Thread {
public void run() {
while (!isInterrupted()){ //非阻塞過程中通過判斷中斷標(biāo)志來(lái)退出
try{
Thread.sleep(5*1000);//阻塞過程捕獲中斷異常來(lái)退出
}catch(InterruptedException e){
e.printStackTrace();
break;//捕獲到異常之后谆奥,執(zhí)行break跳出循環(huán)
}
}
}
}
4.stop方法終止線程(線程不安全)
程序中可以直接使用thread.stop()來(lái)強(qiáng)行終止線程眼坏,但是stop方法是很危險(xiǎn)的拂玻,就象突然關(guān)閉計(jì)算機(jī)電源,而不是按正常程序關(guān)機(jī)一樣宰译,可能會(huì)產(chǎn)生不可預(yù)料的結(jié)果檐蚜,不安全主要是:thread.stop()調(diào)用之后,創(chuàng)建子線程的線程就會(huì)拋出ThreadDeatherror的錯(cuò)誤沿侈,并且會(huì)釋放子線程所持有的所有鎖闯第。一般任何進(jìn)行加鎖的代碼塊,都是為了保護(hù)數(shù)據(jù)的一致性缀拭,如果在調(diào)用thread.stop()后導(dǎo)致了該線程所持有的所有鎖的突然釋放(不可控制)咳短,那么被保護(hù)數(shù)據(jù)就有可能呈現(xiàn)不一致性,其他線程在使用這些被破壞的數(shù)據(jù)時(shí)蛛淋,有可能導(dǎo)致一些很奇怪的應(yīng)用程序錯(cuò)誤咙好。因此,并不推薦使用stop方法來(lái)終止線程褐荷。
sleep與wait 區(qū)別
\1. 對(duì)于sleep()方法勾效,我們首先要知道該方法是屬于Thread類中的。而wait()方法,則是屬于Object類中的层宫。
\2. sleep()方法導(dǎo)致了程序暫停執(zhí)行指定的時(shí)間杨伙,讓出cpu該其他線程,但是他的監(jiān)控狀態(tài)依然保持著萌腿,當(dāng)指定的時(shí)間到了又會(huì)自動(dòng)恢復(fù)運(yùn)行狀態(tài)限匣。
\3. 在調(diào)用sleep()方法的過程中,線程不會(huì)釋放對(duì)象鎖哮奇。
\4. 而當(dāng)調(diào)用wait()方法的時(shí)候膛腐,線程會(huì)放棄對(duì)象鎖,進(jìn)入等待此對(duì)象的等待鎖定池鼎俘,只有針對(duì)此對(duì)象調(diào)用notify()方法后本線程才進(jìn)入對(duì)象鎖定池準(zhǔn)備獲取對(duì)象鎖進(jìn)入運(yùn)行狀態(tài)哲身。
start與run區(qū)別
\1. start()方法來(lái)啟動(dòng)線程,真正實(shí)現(xiàn)了多線程運(yùn)行贸伐。這時(shí)無(wú)需等待run方法體代碼執(zhí)行完畢勘天,可以直接繼續(xù)執(zhí)行下面的代碼。
\2. 通過調(diào)用Thread類的start()方法來(lái)啟動(dòng)一個(gè)線程捉邢, 這時(shí)此線程是處于就緒狀態(tài)脯丝, 并沒有運(yùn)行。
\3. 方法run()稱為線程體伏伐,它包含了要執(zhí)行的這個(gè)線程的內(nèi)容宠进,線程就進(jìn)入了運(yùn)行狀態(tài),開始運(yùn)行run函數(shù)當(dāng)中的代碼藐翎。 Run方法運(yùn)行結(jié)束材蹬, 此線程終止。然后CPU再調(diào)度其它線程吝镣。
JAVA后臺(tái)線程
\1. 定義:守護(hù)線程--也稱“服務(wù)線程”堤器,它是后臺(tái)線程,它有一個(gè)特性末贾,即為用戶線程 提供 公共服務(wù)闸溃,在沒有用戶線程可服務(wù)時(shí)會(huì)自動(dòng)離開。
\2. 優(yōu)先級(jí):守護(hù)線程的優(yōu)先級(jí)比較低拱撵,用于為系統(tǒng)中的其它對(duì)象和線程提供服務(wù)辉川。
\3. 設(shè)置:通過setDaemon(true)來(lái)設(shè)置線程為“守護(hù)線程”;將一個(gè)用戶線程設(shè)置為守護(hù)線程的方式是在 線程對(duì)象創(chuàng)建 之前 用線程對(duì)象的setDaemon方法拴测。
\4. 在Daemon線程中產(chǎn)生的新線程也是Daemon的乓旗。
\5. 線程則是JVM級(jí)別的,以Tomcat 為例昼扛,如果你在Web 應(yīng)用中啟動(dòng)一個(gè)線程寸齐,這個(gè)線程的生命周期并不會(huì)和Web應(yīng)用程序保持同步欲诺。也就是說(shuō),即使你停止了Web應(yīng)用渺鹦,這個(gè)線程依舊是活躍的扰法。
\6. example: 垃圾回收線程就是一個(gè)經(jīng)典的守護(hù)線程,當(dāng)我們的程序中不再有任何運(yùn)行的Thread,程序就不會(huì)再產(chǎn)生垃圾毅厚,垃圾回收器也就無(wú)事可做塞颁,所以當(dāng)垃圾回收線程是JVM上僅剩的線程時(shí),垃圾回收線程會(huì)自動(dòng)離開吸耿。它始終在低級(jí)別的狀態(tài)中運(yùn)行祠锣,用于實(shí)時(shí)監(jiān)控和管理系統(tǒng)中的可回收資源。
\7. 生命周期:守護(hù)進(jìn)程(Daemon)是運(yùn)行在后臺(tái)的一種特殊進(jìn)程咽安。它獨(dú)立于控制終端并且周期性地執(zhí)行某種任務(wù)或等待處理某些發(fā)生的事件伴网。也就是說(shuō)守護(hù)線程不依賴于終端,但是依賴于系統(tǒng)妆棒,與系統(tǒng)“同生共死”澡腾。當(dāng)JVM中所有的線程都是守護(hù)線程的時(shí)候,JVM就可以退出了糕珊;如果還有一個(gè)或以上的非守護(hù)線程則JVM不會(huì)退出动分。
JAVA鎖
樂觀鎖
樂觀鎖是一種樂觀思想,即認(rèn)為讀多寫少红选,遇到并發(fā)寫的可能性低澜公,每次去拿數(shù)據(jù)的時(shí)候都認(rèn)為別人不會(huì)修改,所以不會(huì)上鎖喇肋,但是在更新的時(shí)候會(huì)判斷一下在此期間別人有沒有去更新這個(gè)數(shù)據(jù)坟乾,采取在寫時(shí)先讀出當(dāng)前版本號(hào),然后加鎖操作(比較跟上一次的版本號(hào)苟蹈,如果一樣則更新)糊渊,如果失敗則要重復(fù)讀-比較-寫的操作右核。 java中的樂觀鎖基本都是通過CAS操作實(shí)現(xiàn)的慧脱,CAS是一種更新的原子操作,比較當(dāng)前值跟傳入值是否一樣贺喝,一樣則更新菱鸥,否則失敗。
悲觀鎖
悲觀鎖是就是悲觀思想躏鱼,即認(rèn)為寫多氮采,遇到并發(fā)寫的可能性高,每次去拿數(shù)據(jù)的時(shí)候都認(rèn)為別人會(huì)修改染苛,所以每次在讀寫數(shù)據(jù)的時(shí)候都會(huì)上鎖鹊漠,這樣別人想讀寫這個(gè)數(shù)據(jù)就會(huì)block直到拿到鎖主到。java中的悲觀鎖就是Synchronized,AQS框架下的鎖則是先嘗試cas樂觀鎖去獲取鎖,獲取不到躯概,才會(huì)轉(zhuǎn)換為悲觀鎖登钥,如RetreenLock。
自旋鎖
自旋鎖原理非常簡(jiǎn)單娶靡,如果持有鎖的線程能在很短時(shí)間內(nèi)釋放鎖資源牧牢,那么那些等待競(jìng)爭(zhēng)鎖的線程就不需要做內(nèi)核態(tài)和用戶態(tài)之間的切換進(jìn)入阻塞掛起狀態(tài),它們只需要等一等(自旋)姿锭,等持有鎖的線程釋放鎖后即可立即獲取鎖塔鳍,這樣就避免用戶線程和內(nèi)核的切換的消耗。 線程自旋是需要消耗cup的呻此,說(shuō)白了就是讓cup在做無(wú)用功轮纫,如果一直獲取不到鎖,那線程也不能一直占用cup自旋做無(wú)用功焚鲜,所以需要設(shè)定一個(gè)自旋等待的最大時(shí)間蜡感。 如果持有鎖的線程執(zhí)行的時(shí)間超過自旋等待的最大時(shí)間仍沒有釋放鎖,就會(huì)導(dǎo)致其它爭(zhēng)用鎖的線程在最大等待時(shí)間內(nèi)還是獲取不到鎖恃泪,這時(shí)爭(zhēng)用線程會(huì)停止自旋進(jìn)入阻塞狀態(tài)郑兴。
自旋鎖的優(yōu)缺點(diǎn)自旋鎖盡可能的減少線程的阻塞,這對(duì)于鎖的競(jìng)爭(zhēng)不激烈贝乎,且占用鎖時(shí)間非常短的代碼塊來(lái)說(shuō)性能能大幅度的提升情连,因?yàn)樽孕南臅?huì)小于線程阻塞掛起再喚醒的操作的消耗,這些操作會(huì)導(dǎo)致線程發(fā)生兩次上下文切換览效! 但是如果鎖的競(jìng)爭(zhēng)激烈却舀,或者持有鎖的線程需要長(zhǎng)時(shí)間占用鎖執(zhí)行同步塊,這時(shí)候就不適合使用自旋鎖了锤灿,因?yàn)樽孕i在獲取鎖前一直都是占用cpu做無(wú)用功挽拔,占著XX不XX,同時(shí)有大量線程在競(jìng)爭(zhēng)一個(gè)鎖但校,會(huì)導(dǎo)致獲取鎖的時(shí)間很長(zhǎng)螃诅,線程自旋的消耗大于線程阻塞掛起操作的消耗,其它需要cup的線程又不能獲取到cpu状囱,造成cpu的浪費(fèi)术裸。所以這種情況下我們要關(guān)閉自旋鎖; 自旋鎖時(shí)間閾值(1.6引入了適應(yīng)性自旋鎖) 自旋鎖的目的是為了占著CPU的資源不釋放亭枷,等到獲取到鎖立即進(jìn)行處理袭艺。但是如何去選擇自旋的執(zhí)行時(shí)間呢?如果自旋執(zhí)行時(shí)間太長(zhǎng)叨粘,會(huì)有大量的線程處于自旋狀態(tài)占用CPU資源猾编,進(jìn)而會(huì)影響整體系統(tǒng)的性能瘤睹。因此自旋的周期選的額外重要! 121623125152125125
JVM對(duì)于自旋周期的選擇答倡,jdk1.5這個(gè)限度是一定的寫死的默蚌,在1.6引入了適應(yīng)性自旋鎖,適應(yīng)性自旋鎖意味著自旋的時(shí)間不在是固定的了苇羡,而是由前一次在同一個(gè)鎖上的自旋時(shí)間以及鎖的擁有者的狀態(tài)來(lái)決定绸吸,基本認(rèn)為一個(gè)線程上下文切換的時(shí)間是最佳的一個(gè)時(shí)間,同時(shí)JVM還針對(duì)當(dāng)前CPU的負(fù)荷情況做了較多的優(yōu)化设江,如果平均負(fù)載小于CPUs則一直自旋锦茁,如果有超過(CPUs/2)個(gè)線程正在自旋,則后來(lái)線程直接阻塞叉存,如果正在自旋的線程發(fā)現(xiàn)Owner發(fā)生了變化則延遲自旋時(shí)間(自旋計(jì)數(shù))或進(jìn)入阻塞码俩,如果CPU處于節(jié)電模式則停止自旋,自旋時(shí)間的最壞情況是CPU的存儲(chǔ)延遲(CPU A存儲(chǔ)了一個(gè)數(shù)據(jù)歼捏,到CPU B得知這個(gè)數(shù)據(jù)直接的時(shí)間差)稿存,自旋時(shí)會(huì)適當(dāng)放棄線程優(yōu)先級(jí)之間的差異。
自旋鎖的開啟
JDK1.6中-XX:+UseSpinning開啟瞳秽;
-XX:PreBlockSpin=10 為自旋次數(shù)瓣履;
JDK1.7后,去掉此參數(shù)练俐,由jvm控制袖迎;
Synchronized同步鎖
synchronized它可以把任意一個(gè)非NULL的對(duì)象當(dāng)作鎖。他屬于獨(dú)占式的悲觀鎖腺晾,同時(shí)屬于可重入鎖燕锥。
<u style="text-decoration: none; border-bottom: 1px dashed grey;">Synchronized作用范圍</u>
\1. 作用于方法時(shí),鎖住的是對(duì)象的實(shí)例(this)悯蝉;
\2. 當(dāng)作用于靜態(tài)方法時(shí)归形,鎖住的是Class實(shí)例,又因?yàn)镃lass的相關(guān)數(shù)據(jù)存儲(chǔ)在永久帶PermGen(jdk1.8則是metaspace)鼻由,永久帶是全局共享的暇榴,因此靜態(tài)方法鎖相當(dāng)于類的一個(gè)全局鎖,會(huì)鎖所有調(diào)用該方法的線程嗡靡;
\3. synchronized作用于一個(gè)對(duì)象實(shí)例時(shí)跺撼,鎖住的是所有以該對(duì)象為鎖的代碼塊窟感。它有多個(gè)隊(duì)列讨彼,當(dāng)多個(gè)線程一起訪問某個(gè)對(duì)象監(jiān)視器的時(shí)候,對(duì)象監(jiān)視器會(huì)將這些線程存儲(chǔ)在不同的容器中柿祈。
<u style="text-decoration: none; border-bottom: 1px dashed grey;">Synchronized核心組件</u>
- Wait Set:那些調(diào)用wait方法被阻塞的線程被放置在這里哈误;
- Contention List:競(jìng)爭(zhēng)隊(duì)列哩至,所有請(qǐng)求鎖的線程首先被放在這個(gè)競(jìng)爭(zhēng)隊(duì)列中;
- Entry List:Contention List中那些有資格成為候選資源的線程被移動(dòng)到Entry List中蜜自;
- OnDeck:任意時(shí)刻菩貌,最多只有一個(gè)線程正在競(jìng)爭(zhēng)鎖資源,該線程被稱為OnDeck重荠;
- Owner:當(dāng)前已經(jīng)獲取到所資源的線程被稱為Owner箭阶; 6) !Owner:當(dāng)前釋放鎖的線程。
<u style="text-decoration: none; border-bottom: 1px dashed grey;">Synchronized實(shí)現(xiàn)</u>
\1. JVM每次從隊(duì)列的尾部取出一個(gè)數(shù)據(jù)用于鎖競(jìng)爭(zhēng)候選者(OnDeck)戈鲁,但是并發(fā)情況下仇参,ContentionList會(huì)被大量的并發(fā)線程進(jìn)行CAS訪問,為了降低對(duì)尾部元素的競(jìng)爭(zhēng)婆殿,JVM會(huì)將一部分線程移動(dòng)到EntryList中作為候選競(jìng)爭(zhēng)線程诈乒。
\2. Owner線程會(huì)在unlock時(shí),將ContentionList中的部分線程遷移到EntryList中婆芦,并指定EntryList中的某個(gè)線程為OnDeck線程(一般是最先進(jìn)入的那個(gè)線程)怕磨。
\3. Owner線程并不直接把鎖傳遞給OnDeck線程,而是把鎖競(jìng)爭(zhēng)的權(quán)利交給OnDeck消约,OnDeck需要重新競(jìng)爭(zhēng)鎖肠鲫。這樣雖然犧牲了一些公平性,但是能極大的提升系統(tǒng)的吞吐量或粮,在JVM中滩届,也把這種選擇行為稱之為“競(jìng)爭(zhēng)切換”。
\4. OnDeck線程獲取到鎖資源后會(huì)變?yōu)镺wner線程被啼,而沒有得到鎖資源的仍然停留在EntryList中帜消。如果Owner線程被wait方法阻塞,則轉(zhuǎn)移到WaitSet隊(duì)列中浓体,直到某個(gè)時(shí)刻通過notify或者notifyAll喚醒泡挺,會(huì)重新進(jìn)入EntryList中。
\5. 處于ContentionList命浴、EntryList娄猫、WaitSet中的線程都處于阻塞狀態(tài),該阻塞是由操作系統(tǒng)來(lái)完成的(Linux內(nèi)核下采用pthread_mutex_lock內(nèi)核函數(shù)實(shí)現(xiàn)的)生闲。
\6. Synchronized是非公平鎖媳溺。 Synchronized在線程進(jìn)入ContentionList時(shí),等待的線程會(huì)先嘗試自旋獲取鎖碍讯,如果獲取不到就進(jìn)入ContentionList悬蔽,這明顯對(duì)于已經(jīng)進(jìn)入隊(duì)列的線程是不公平的,還有一個(gè)不公平的事情就是自旋獲取鎖的線程還可能直接搶占OnDeck線程的鎖資源捉兴。
\7. 每個(gè)對(duì)象都有個(gè)monitor對(duì)象蝎困,假說(shuō)就是在競(jìng)爭(zhēng)monitor對(duì)象录语,代碼塊加鎖是在前后分別加上monitorenter和monitorexit指令來(lái)實(shí)現(xiàn)的,方法加鎖是通過一個(gè)標(biāo)記位來(lái)判斷的
\8. synchronized是一個(gè)重量級(jí)操作禾乘,需要調(diào)用操作系統(tǒng)相關(guān)接口澎埠,性能是低效的,有可能給線程加鎖消耗的時(shí)間比有用操作消耗的時(shí)間更多始藕。
\9. Java1.6蒲稳,synchronized進(jìn)行了很多的優(yōu)化,有適應(yīng)自旋伍派、鎖消除弟塞、鎖粗化、輕量級(jí)鎖及偏向鎖等拙已,效率有了本質(zhì)上的提高决记。在之后推出的Java1.7與1.8中,均對(duì)該關(guān)鍵字的實(shí)現(xiàn)機(jī)理做了優(yōu)化倍踪。引入了偏向鎖和輕量級(jí)鎖系宫。都是在對(duì)象頭中有標(biāo)記位,不需要經(jīng)過操作系統(tǒng)加鎖建车。
\10. 鎖可以從偏向鎖升級(jí)到輕量級(jí)鎖扩借,再升級(jí)到重量級(jí)鎖。這種升級(jí)過程叫做鎖膨脹缤至;
\11. JDK 1.6中默認(rèn)是開啟偏向鎖和輕量級(jí)鎖潮罪,可以通過-XX:-UseBiasedLocking來(lái)禁用偏向鎖。
ReentrantLock
ReentantLock繼承接口Lock并實(shí)現(xiàn)了接口中定義的方法领斥,它是一種可重入鎖嫉到,除了能完成synchronized所能完成的所有工作外,還提供了諸如可響應(yīng)中斷鎖月洛、可輪詢鎖請(qǐng)求何恶、定時(shí)鎖等避免多線程死鎖的方法。
<u style="text-decoration: none; border-bottom: 1px dashed grey;">Lock接口的主要方法</u>
\1. void lock(): 執(zhí)行此方法時(shí), 如果鎖處于空閑狀態(tài), 當(dāng)前線程將獲取到鎖. 相反, 如果鎖已經(jīng)被其他線程持有, 將禁用當(dāng)前線程, 直到當(dāng)前線程獲取到鎖.
\2. boolean tryLock():如果鎖可用, 則獲取鎖, 并立即返回true, 否則返回false. 該方法和lock()的區(qū)別在于, tryLock()只是"試圖"獲取鎖, 如果鎖不可用, 不會(huì)導(dǎo)致當(dāng)前線程被禁用, 當(dāng)前線程仍然繼續(xù)往下執(zhí)行代碼. 而lock()方法則是一定要獲取到鎖, 如果鎖不可用, 就一直等待, 在未獲得鎖之前,當(dāng)前線程并不繼續(xù)向下執(zhí)行.
\3. void unlock():執(zhí)行此方法時(shí), 當(dāng)前線程將釋放持有的鎖. 鎖只能由持有者釋放, 如果線程并不持有鎖, 卻執(zhí)行該方法, 可能導(dǎo)致異常的發(fā)生.
\4. Condition newCondition():條件對(duì)象嚼黔,獲取等待通知組件细层。該組件和當(dāng)前的鎖綁定,當(dāng)前線程只有獲取了鎖唬涧,才能調(diào)用該組件的await()方法疫赎,而調(diào)用后,當(dāng)前線程將縮放鎖碎节。
\5. getHoldCount() :查詢當(dāng)前線程保持此鎖的次數(shù)捧搞,也就是執(zhí)行此線程執(zhí)行l(wèi)ock方法的次數(shù)。
\6. getQueueLength():返回正等待獲取此鎖的線程估計(jì)數(shù),比如啟動(dòng)10個(gè)線程实牡,1個(gè)線程獲得鎖陌僵,此時(shí)返回的是9
\7. getWaitQueueLength:(Condition condition)返回等待與此鎖相關(guān)的給定條件的線程估計(jì)數(shù)轴合。比如10個(gè)線程创坞,用同一個(gè)condition對(duì)象,并且此時(shí)這10個(gè)線程都執(zhí)行了condition對(duì)象的await方法受葛,那么此時(shí)執(zhí)行此方法返回10
\8. hasWaiters(Condition condition):查詢是否有線程等待與此鎖有關(guān)的給定條件(condition)题涨,對(duì)于指定contidion對(duì)象,有多少線程執(zhí)行了condition.await方法
\9. hasQueuedThread(Thread thread):查詢給定線程是否等待獲取此鎖
\10. hasQueuedThreads():是否有線程等待此鎖
\11. isFair():該鎖是否公平鎖
\12. isHeldByCurrentThread(): 當(dāng)前線程是否保持鎖鎖定总滩,線程的執(zhí)行l(wèi)ock方法的前后分別是false和true
\13. isLock():此鎖是否有任意線程占用
\14. lockInterruptibly():如果當(dāng)前線程未被中斷纲堵,獲取鎖
\15. tryLock():嘗試獲得鎖,僅在調(diào)用時(shí)鎖未被線程占用闰渔,獲得鎖
\16. tryLock(long timeout TimeUnit unit):如果鎖在給定等待時(shí)間內(nèi)沒有被另一個(gè)線程保持席函,則獲取該鎖。
線程基本方法
線程相關(guān)的基本方法有wait冈涧,notify茂附,notifyAll,sleep督弓,join营曼,yield等。
1.線程等待(wait)
調(diào)用該方法的線程進(jìn)入WAITING狀態(tài)愚隧,只有等待另外線程的通知或被中斷才會(huì)返回蒂阱,需要注意的是調(diào)用wait()方法后,會(huì)釋放對(duì)象的鎖狂塘。因此录煤,wait方法一般用在同步方法或同步代碼塊中。
2. 線程睡眠(sleep)
sleep導(dǎo)致當(dāng)前線程休眠荞胡,與wait方法不同的是sleep不會(huì)釋放當(dāng)前占有的鎖,sleep(long)會(huì)導(dǎo)致線程進(jìn)入TIMED-WATING狀態(tài)辐赞,而wait()方法會(huì)導(dǎo)致當(dāng)前線程進(jìn)入WATING狀態(tài)
3. 線程讓步(yield)
yield會(huì)使當(dāng)前線程讓出CPU執(zhí)行時(shí)間片,與其他線程一起重新競(jìng)爭(zhēng)CPU時(shí)間片硝训。一般情況下响委,優(yōu)先級(jí)高的線程有更大的可能性成功競(jìng)爭(zhēng)得到CPU時(shí)間片,但這又不是絕對(duì)的窖梁,有的操作系統(tǒng)對(duì)線程優(yōu)先級(jí)并不敏感赘风。
4.線程中斷(interrupt)
內(nèi)部的一個(gè)中斷標(biāo)識(shí)位。這個(gè)線程本身并不會(huì)因此而改變狀態(tài)(如阻塞纵刘,終止等)邀窃。
\1. 調(diào)用interrupt()方法并不會(huì)中斷一個(gè)正在運(yùn)行的線程。也就是說(shuō)處于Running狀態(tài)的線程并不會(huì)因?yàn)楸恢袛喽唤K止,僅僅改變了內(nèi)部維護(hù)的中斷標(biāo)識(shí)位而已瞬捕。
\2. 若調(diào)用sleep()而使線程處于TIMED-WATING狀態(tài)鞍历,這時(shí)調(diào)用interrupt()方法,會(huì)拋出InterruptedException,從而使線程提前結(jié)束TIMED-WATING狀態(tài)肪虎。
\3. 許多聲明拋出InterruptedException的方法(如Thread.sleep(long mills方法))劣砍,拋出異常前,都會(huì)清除中斷標(biāo)識(shí)位扇救,所以拋出異常后刑枝,調(diào)用isInterrupted()方法將會(huì)返回false。
\4. 中斷狀態(tài)是線程固有的一個(gè)標(biāo)識(shí)位迅腔,可以通過此標(biāo)識(shí)位安全的終止線程装诡。比如,你想終止一個(gè)線程thread的時(shí)候伍伤,可以調(diào)用thread.interrupt()方法抄邀,在線程的run方法內(nèi)部可以根據(jù)thread.isInterrupted()的值來(lái)優(yōu)雅的終止線程喂击。
5. Join等待其他線程終止
join() 方法,等待其他線程終止锌雀,在當(dāng)前線程中調(diào)用一個(gè)線程的 join() 方法蚂夕,則當(dāng)前線程轉(zhuǎn)為阻塞狀態(tài),回到另一個(gè)線程結(jié)束汤锨,當(dāng)前線程再由阻塞狀態(tài)變?yōu)榫途w狀態(tài)双抽,等待 cpu 的寵幸。
\6. 為什么要用join()方法闲礼?
很多情況下牍汹,主線程生成并啟動(dòng)了子線程,需要用到子線程返回的結(jié)果柬泽,也就是需要主線程需要在子線程結(jié)束后再結(jié)束慎菲,這時(shí)候就要用到 join() 方法。
System.out.println(Thread.currentThread().getName() + "線程運(yùn)行開始!");
Thread6 thread1 = new Thread6();
thread1.setName("線程B");
thread1.join();
System.out.println("這時(shí)thread1執(zhí)行完畢之后才能執(zhí)行主線程");
\7. 線程喚醒(notify)
Object 類中的 notify() 方法锨并,喚醒在此對(duì)象監(jiān)視器上等待的單個(gè)線程露该,如果所有線程都在此對(duì)象上等待,則會(huì)選擇喚醒其中一個(gè)線程第煮,選擇是任意的解幼,并在對(duì)實(shí)現(xiàn)做出決定時(shí)發(fā)生,線程通過調(diào)用其中一個(gè) wait() 方法包警,在對(duì)象的監(jiān)視器上等待撵摆,直到當(dāng)前的線程放棄此對(duì)象上的鎖定,才能繼續(xù)執(zhí)行被喚醒的線程害晦,被喚醒的線程將以常規(guī)方式與在該對(duì)象上主動(dòng)同步的其他所有線程進(jìn)行競(jìng)爭(zhēng)特铝。類似的方法還有 notifyAll() ,喚醒再次監(jiān)視器上等待的所有線程。
\8. 其他方法:
\1. sleep():強(qiáng)迫一個(gè)線程睡眠N毫秒鲫剿。
\2. isAlive(): 判斷一個(gè)線程是否存活鳄逾。
\3. join(): 等待線程終止。
\4. activeCount(): 程序中活躍的線程數(shù)灵莲。
\5. enumerate(): 枚舉程序中的線程雕凹。
\6. currentThread(): 得到當(dāng)前線程。
\7. isDaemon(): 一個(gè)線程是否為守護(hù)線程笆呆。
\8. setDaemon(): 設(shè)置一個(gè)線程為守護(hù)線程请琳。(用戶線程和守護(hù)線程的區(qū)別在于粱挡,是否等待主線程依賴于主線程結(jié)束而結(jié)束)
\9. setName(): 為線程設(shè)置一個(gè)名稱赠幕。
\10. wait(): 強(qiáng)迫一個(gè)線程等待。
\11. notify(): 通知一個(gè)線程繼續(xù)運(yùn)行询筏。
\12. setPriority(): 設(shè)置一個(gè)線程的優(yōu)先級(jí)榕堰。
\13. getPriority()::獲得一個(gè)線程的優(yōu)先級(jí)。
線程上下文切換
巧妙地利用了時(shí)間的輪轉(zhuǎn)的方式, CPU給每個(gè)任務(wù)都服務(wù)一定的時(shí)間嫌套,然后把當(dāng)前任務(wù)的狀態(tài)保存下來(lái)逆屡,在加載下一任務(wù)的狀態(tài)后,繼續(xù)服務(wù)下一任務(wù)踱讨,任務(wù)的狀態(tài)保存及再加載, 這段過程就叫做上下文切換魏蔗。時(shí)間的輪轉(zhuǎn)的方式使多個(gè)任務(wù)在同一顆CPU上執(zhí)行變成了可能。
\1. 進(jìn)程
(有時(shí)候也稱做任務(wù))是指一個(gè)程序運(yùn)行的實(shí)例痹筛。在Linux系統(tǒng)中莺治,線程就是能并行運(yùn)行并且與他們的父進(jìn)程(創(chuàng)建他們的進(jìn)程)共享同一地址空間(一段內(nèi)存區(qū)域)和其他資源的輕量級(jí)的進(jìn)程。
\2. 上下文
是指某一時(shí)間點(diǎn) CPU 寄存器和程序計(jì)數(shù)器的內(nèi)容帚稠。
\3. 寄存器
是 CPU 內(nèi)部的數(shù)量較少但是速度很快的內(nèi)存(與之對(duì)應(yīng)的是 CPU 外部相對(duì)較慢的 RAM 主內(nèi)存)谣旁。寄存器通過對(duì)常用值(通常是運(yùn)算的中間值)的快速訪問來(lái)提高計(jì)算機(jī)程序運(yùn)行的速度。
\4. 程序計(jì)數(shù)器
是一個(gè)專用的寄存器滋早,用于表明指令序列中 CPU 正在執(zhí)行的位置榄审,存的值為正在執(zhí)行的指令的位置或者下一個(gè)將要被執(zhí)行的指令的位置,具體依賴于特定的系統(tǒng)杆麸。
\5. PCB-“切換楨”
上下文切換可以認(rèn)為是內(nèi)核(操作系統(tǒng)的核心)在 CPU 上對(duì)于進(jìn)程(包括線程)進(jìn)行切換搁进,上下文切換過程中的信息是保存在進(jìn)程控制塊(PCB, process control block)中的。PCB還經(jīng)常被稱作“切換楨”(switchframe)昔头。信息會(huì)一直保存到CPU的內(nèi)存中饼问,直到它們被再次使用。 121623125152125125
\6. 上下文切換的活動(dòng):
\1. 掛起一個(gè)進(jìn)程减细,將這個(gè)進(jìn)程在 CPU 中的狀態(tài)(上下文)存儲(chǔ)于內(nèi)存中的某處匆瓜。
\2. 在內(nèi)存中檢索下一個(gè)進(jìn)程的上下文并將其在 CPU 的寄存器中恢復(fù)。
\3. 跳轉(zhuǎn)到程序計(jì)數(shù)器所指向的位置(即跳轉(zhuǎn)到進(jìn)程被中斷時(shí)的代碼行),以恢復(fù)該進(jìn)程在程序中驮吱。
\7. 引起線程上下文切換的原因
\1. 當(dāng)前執(zhí)行任務(wù)的時(shí)間片用完之后茧妒,系統(tǒng)CPU正常調(diào)度下一個(gè)任務(wù);
\2. 當(dāng)前執(zhí)行任務(wù)碰到IO阻塞左冬,調(diào)度器將此任務(wù)掛起桐筏,繼續(xù)下一任務(wù);
\3. 多個(gè)任務(wù)搶占鎖資源拇砰,當(dāng)前任務(wù)沒有搶到鎖資源梅忌,被調(diào)度器掛起,繼續(xù)下一任務(wù)除破;
\4. 用戶代碼掛起當(dāng)前任務(wù)牧氮,讓出CPU時(shí)間;
\5. 硬件中斷瑰枫;
同步鎖與死鎖
\1. 同步鎖
當(dāng)多個(gè)線程同時(shí)訪問同一個(gè)數(shù)據(jù)時(shí)踱葛,很容易出現(xiàn)問題。為了避免這種情況出現(xiàn)光坝,我們要保證線程同步互斥尸诽,就是指并發(fā)執(zhí)行的多個(gè)線程,在同一時(shí)間內(nèi)只允許一個(gè)線程訪問共享數(shù)據(jù)盯另。 Java中可以使用synchronized關(guān)鍵字來(lái)取得一個(gè)對(duì)象的同步鎖性含。
\2. 死鎖
何為死鎖,就是多個(gè)線程同時(shí)被阻塞鸳惯,它們中的一個(gè)或者全部都在等待某個(gè)資源被釋放商蕴。
\3. 線程池原理
線程池做的工作主要是控制運(yùn)行的線程的數(shù)量,處理過程中將任務(wù)放入隊(duì)列悲敷,然后在線程創(chuàng)建后啟動(dòng)這些任務(wù)究恤,如果線程數(shù)量超過了最大數(shù)量超出數(shù)量的線程排隊(duì)等候,等其它線程執(zhí)行完畢后德,再?gòu)年?duì)列中取出任務(wù)來(lái)執(zhí)行部宿。他的主要特點(diǎn)為:線程復(fù)用;控制最大并發(fā)數(shù)瓢湃;管理線程理张。
\1. 線程復(fù)用 每一個(gè) Thread 的類都有一個(gè) start 方法。 當(dāng)調(diào)用start啟動(dòng)線程時(shí)Java虛擬機(jī)會(huì)調(diào)用該類的 run 方法绵患。 那么該類的 run() 方法中就是調(diào)用了 Runnable 對(duì)象的 run() 方法雾叭。 我們可以繼承重寫 Thread 類,在其 start 方法中添加不斷循環(huán)調(diào)用傳遞過來(lái)的 Runnable 對(duì)象落蝙。 這就是線程池的實(shí)現(xiàn)原理织狐。循環(huán)方法中不斷獲取 Runnable 是用 Queue 實(shí)現(xiàn)的暂幼,在獲取下一個(gè) Runnable 之前可以是阻塞的。
.2. 線程池的組成
一般的線程池主要分為以下4個(gè)組成部分:
\1. 線程池管理器:用于創(chuàng)建并管理線程池
\2. 工作線程:線程池中的線程
\3. 任務(wù)接口:每個(gè)任務(wù)必須實(shí)現(xiàn)的接口移迫,用于工作線程調(diào)度其運(yùn)行
\4. 任務(wù)隊(duì)列:用于存放待處理的任務(wù)旺嬉,提供一種緩沖機(jī)制 Java中的線程池是通過Executor框架實(shí)現(xiàn)的,該框架中用到了Executor厨埋,Executors邪媳,ExecutorService,ThreadPoolExecutor 荡陷,Callable和Future雨效、FutureTask這幾個(gè)類。
ThreadPoolExecutor的構(gòu)造方法如下:
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, Executors.defaultThreadFactory(), defaultHandler); }
\1. corePoolSize:指定了線程池中的線程數(shù)量废赞。
\2. maximumPoolSize:指定了線程池中的最大線程數(shù)量徽龟。
\3. keepAliveTime:當(dāng)前線程池?cái)?shù)量超過corePoolSize時(shí),多余的空閑線程的存活時(shí)間蛹头,即多次時(shí)間內(nèi)會(huì)被銷毀顿肺。
\4. unit:keepAliveTime的單位戏溺。
\5. workQueue:任務(wù)隊(duì)列渣蜗,被提交但尚未被執(zhí)行的任務(wù)。
\6. threadFactory:線程工廠旷祸,用于創(chuàng)建線程耕拷,一般用默認(rèn)的即可。
\7. handler:拒絕策略托享,當(dāng)任務(wù)太多來(lái)不及處理骚烧,如何拒絕任務(wù)。
\3. 拒絕策略
線程池中的線程已經(jīng)用完了闰围,無(wú)法繼續(xù)為新任務(wù)服務(wù)赃绊,同時(shí),等待隊(duì)列也已經(jīng)排滿了羡榴,再也塞不下新任務(wù)了碧查。這時(shí)候我們就需要拒絕策略機(jī)制合理的處理這個(gè)問題。 JDK內(nèi)置的拒絕策略如下:
\1. AbortPolicy : 直接拋出異常校仑,阻止系統(tǒng)正常運(yùn)行忠售。
\2. CallerRunsPolicy : 只要線程池未關(guān)閉,該策略直接在調(diào)用者線程中迄沫,運(yùn)行當(dāng)前被丟棄的任務(wù)稻扬。顯然這樣做不會(huì)真的丟棄任務(wù),但是羊瘩,任務(wù)提交線程的性能極有可能會(huì)急劇下降泰佳。
\3. DiscardOldestPolicy : 丟棄最老的一個(gè)請(qǐng)求盼砍,也就是即將被執(zhí)行的一個(gè)任務(wù),并嘗試再次提交當(dāng)前任務(wù)逝她。
\4. DiscardPolicy : 該策略默默地丟棄無(wú)法處理的任務(wù)衬廷,不予任何處理。如果允許任務(wù)丟失汽绢,這是最好的一種方案吗跋。 以上內(nèi)置拒絕策略均實(shí)現(xiàn)了RejectedExecutionHandler接口,若以上策略仍無(wú)法滿足實(shí)際需要宁昭,完全可以自己擴(kuò)展RejectedExecutionHandler接口跌宛。
\4. Java線程池工作過程
\1. 線程池剛創(chuàng)建時(shí),里面沒有一個(gè)線程积仗。任務(wù)隊(duì)列是作為參數(shù)傳進(jìn)來(lái)的疆拘。不過,就算隊(duì)列里面有任務(wù)寂曹,線程池也不會(huì)馬上執(zhí)行它們哎迄。
\2. 當(dāng)調(diào)用 execute() 方法添加一個(gè)任務(wù)時(shí),線程池會(huì)做如下判斷:
a) 如果正在運(yùn)行的線程數(shù)量小于 corePoolSize隆圆,那么馬上創(chuàng)建線程運(yùn)行這個(gè)任務(wù)漱挚;
b) 如果正在運(yùn)行的線程數(shù)量大于或等于 corePoolSize,那么將這個(gè)任務(wù)放入隊(duì)列渺氧;
c) 如果這時(shí)候隊(duì)列滿了旨涝,而且正在運(yùn)行的線程數(shù)量小于 maximumPoolSize,那么還是要?jiǎng)?chuàng)建非核心線程立刻運(yùn)行這個(gè)任務(wù)侣背; d) 如果隊(duì)列滿了白华,而且正在運(yùn)行的線程數(shù)量大于或等于 maximumPoolSize,那么線程池會(huì)拋出異常RejectExecutionException贩耐。
\3. 當(dāng)一個(gè)線程完成任務(wù)時(shí)弧腥,它會(huì)從隊(duì)列中取下一個(gè)任務(wù)來(lái)執(zhí)行。
\4. 當(dāng)一個(gè)線程無(wú)事可做潮太,超過一定的時(shí)間(keepAliveTime)時(shí)管搪,線程池會(huì)判斷,如果當(dāng)前運(yùn)行的線程數(shù)大于 corePoolSize消别,那么這個(gè)線程就被停掉抛蚤。所以線程池的所有任務(wù)完成后,它最終會(huì)收縮到 corePoolSize 的大小寻狂。