1.為什么要使用多線程
- 充分發(fā)揮多核CPU的性能
- 方便進(jìn)行業(yè)務(wù)拆分,提升服務(wù)性能
2. java多線程有什么缺點(diǎn)
(1) 頻繁的上下文切換
線程在切換過程中浮庐,CPU需要保存當(dāng)前線程的狀態(tài)雾叭,以便切換回來時(shí)能夠恢復(fù)到當(dāng)前狀態(tài)夷陋,這個(gè)過程會(huì)損耗CPU性能。頻繁的上下文切換無法發(fā)揮多線程的優(yōu)勢汤纸。為了減少上下文切換衩茸,可以采用無鎖并發(fā)編程、CAS算法贮泞、使用最少的線程或是使用協(xié)程
- 無鎖并發(fā)編程:在有鎖并發(fā)場景中楞慈,線程會(huì)因?yàn)闆]有競爭到鎖而阻塞,讓出CPU啃擦,提前進(jìn)行線程切換
- CAS操作囊蓝,只有一個(gè)線程能夠執(zhí)行成功,其他線程會(huì)循環(huán)競爭鎖令蛉,直到時(shí)間片執(zhí)行完成
- 使用少量線程:任務(wù)較少時(shí)聚霜,避免創(chuàng)建過多的線程,以至于多個(gè)線程處于等待狀態(tài)
- 使用協(xié)程
協(xié)程概念:基于線程之上珠叔,但是比線程更輕量級的存在蝎宇,由程序員自己寫程序控制
協(xié)程的目的:當(dāng)線程出現(xiàn)長時(shí)間IO時(shí),由程序控制运杭,掛起當(dāng)前任務(wù)夫啊,并保存當(dāng)前棧信息,去執(zhí)行另一個(gè)任務(wù)辆憔,等待任務(wù)完成或是達(dá)到某個(gè)條件時(shí)撇眯,再還原原先的棧信息,并繼續(xù)執(zhí)行虱咧。
協(xié)程特點(diǎn):
(1)線程有OS進(jìn)行調(diào)度熊榛,協(xié)程由用戶自己進(jìn)行調(diào)度。且協(xié)程是在同一線程內(nèi)部操作腕巡,所以可以減少線程上下文的切換
(2)線程默認(rèn)的stack是1M玄坦,而協(xié)程默認(rèn)是接近1k,所以一個(gè)線程內(nèi)部可以有多個(gè)協(xié)程
(3)協(xié)程適用于存在阻塞的并發(fā)場景绘沉,而不適用于大量計(jì)算的場景
(2) 線程安全問題
在多線程環(huán)境下煎楣,無論線程以何種順序執(zhí)行,都能保證程序的正確性车伞。線程安全問題择懂,本質(zhì)就是對共享數(shù)據(jù)的訪問問題
3. java線程狀態(tài)
(1) 新建(new):創(chuàng)建后尚未啟動(dòng)的線程處于這個(gè)狀態(tài)
(2) 運(yùn)行(runable): ready + running
- ready:就緒狀態(tài),等待cpu分配時(shí)間片即可運(yùn)行
- running:正在運(yùn)行
(3) 無限等待(waiting):處于這個(gè)狀態(tài)的線程不會(huì)被cpu分配時(shí)間片另玖,其等待其他線程顯示喚醒困曙。有如下方法可以進(jìn)入該狀態(tài):
- 沒有設(shè)置timeout的Object.wait()方法
- 沒有設(shè)置timeout的Thread.join()方法
- LockSupport.park()方法
(4) 超時(shí)等待(time_waiting):該狀態(tài)下表伦,線程也不會(huì)被CPU配時(shí)間片,但是與Waiting不同的是慷丽,該狀態(tài)無需等待其他線程顯示喚醒蹦哼,超過超時(shí)時(shí)間之后,系統(tǒng)會(huì)自動(dòng)喚醒線程要糊。有如下方法可以讓線程進(jìn)入超時(shí)等待狀態(tài)
- Thread.sleep()
- 設(shè)置了timeout的Object.wait()方法
- 設(shè)置了timeout的Thread.join()方法
- LockSupport.parkNanos()方法
- LockSupport.parkUtils()方法
(5) 阻塞(Blocked): 線程因?yàn)槟撤N原因放棄CPU使用權(quán)纲熏,暫時(shí)停止運(yùn)行。阻塞狀態(tài)分兩種
- 同步阻塞:線程在進(jìn)入同步代碼塊(synchronize)時(shí)杨耙,未獲得鎖將進(jìn)入這個(gè)狀態(tài)
- 其他阻塞:正在運(yùn)行的線程發(fā)出IO請求赤套,JVM會(huì)把該線程置為阻塞狀態(tài)
(6) 終止(Terminated):已經(jīng)終止的線程狀態(tài)。線程run()方法或是main()方法執(zhí)行結(jié)束珊膜,或是線程因異常退run方法容握,該線程結(jié)束生命周期
4. 線程生命周期內(nèi)的一些操作
除了新建線程之外,線程生命周期內(nèi)還有一些其他操作车柠,這些操作可以作為線程間的一種通信方式
- 中斷 interrupt
(1) 中斷是什么
interrupt()就是中斷某個(gè)線程剔氏,主要用于線程間的協(xié)作,如果A線程需要中斷B線程竹祷,就調(diào)用B.interrupt()
(2) interrupt()一定會(huì)中斷線程嗎
interrupt()可以看做是線程的一個(gè)標(biāo)志位谈跛,某個(gè)線程被中斷之后,就會(huì)記錄該中斷狀態(tài)塑陵。所以調(diào)用某個(gè)線程的interrupt()之后感憾,并不一定會(huì)真正中斷該線程,僅僅是告知該線程你該中斷了令花,線程是否中斷應(yīng)該由線程自身判斷阻桅,而不是由外部線程決定
(3) 線程中斷狀態(tài)怎么用
原則上,在設(shè)計(jì)線程的執(zhí)行流程時(shí)兼都,首先要判斷線程的中斷狀態(tài)來決定執(zhí)行內(nèi)容
(4) 什么時(shí)候會(huì)拋出InterrupedException
線程處于wait()嫂沉、sleep()、join()等狀態(tài)扮碧,此時(shí)調(diào)用線程的interrupt()會(huì)拋出InterrupedException趟章,且中斷狀態(tài)會(huì)被清除
(5) 拋出的InterruptException該怎么處理- 繼續(xù)上拋,交由上游處理
- catch異常慎王,調(diào)用interrupt()方法蚓土,恢復(fù)當(dāng)前線程的中斷狀態(tài)(調(diào)用線程的interrupt方法,需要通過中斷狀態(tài)判斷線程是否被中斷赖淤,如果不恢復(fù)北戏,就可能會(huì)導(dǎo)致狀態(tài)判斷失敗)
public class ThreadInterruptDemo {
public static void main(String[] args) {
Thread sleepThread1 = new Thread(()-> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread sleepThread2 = new Thread(()-> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
//重置中斷狀態(tài)
Thread.interrupted();
e.printStackTrace();
}
});
Thread busyThread = new Thread(()-> {
while(true){
}
});
sleepThread1.start();
sleepThread2.start();
busyThread.start();
sleepThread1.interrupt();
sleepThread2.interrupt();
busyThread.interrupt();
System.out.println("sleepThread1 isInterrupt = " + sleepThread1.isInterrupted());
System.out.println("sleepThread2 isInterrupt = " + sleepThread2.isInterrupted());
System.out.println("busyThread isInterrupt = " + busyThread.isInterrupted());
}
}
- join
join可以看作是線程間的一種協(xié)作方式漫蛔,在很多時(shí)候嗜愈,一個(gè)線程能否執(zhí)行,依賴另一個(gè)線程的執(zhí)行結(jié)果莽龟,當(dāng)A線程依賴B線程時(shí)蠕嫁,可以調(diào)用B.jion(),阻塞一直到B線程執(zhí)行完成
join方法的核心源碼毯盈,判斷鎖等待線程isAlive剃毒,如果存活,則無限期等待搂赋,當(dāng)B線程執(zhí)行完成退出時(shí)赘阀,會(huì)調(diào)用notifyAll()方法,通知所有線程
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
//無超時(shí)的join()脑奠,無限期等待
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
//有超時(shí)的等待
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
//java join demo
public class THreadJoinDemo {
public static void main(String[] args) {
Thread previousThread = Thread.currentThread();
for (int i = 0; i < 10; i++) {
TestJoin testJoin = new TestJoin(previousThread);
testJoin.start();
previousThread = testJoin;
}
}
}
class TestJoin extends Thread {
Thread currentThread;
public TestJoin(Thread currentThread) {
this.currentThread = currentThread;
}
@Override
public void run() {
try {
currentThread.join();
System.out.println(currentThread.getName() + " terminated");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
sleep
sleep就是按照當(dāng)前指定的時(shí)間休眠基公,時(shí)間精度取決于處理器的計(jì)時(shí)器和調(diào)度器。
sleep對比wait
(1) sleep是Thread類的靜態(tài)方法宋欺,wait是Object類的實(shí)例方法
(2) sleep可以在任意地方使用轰豆;wait只能在同步代碼塊或是同步方法中使用,也就是對象已經(jīng)獲得鎖齿诞。調(diào)用wait之后酸休,會(huì)釋放鎖,線程進(jìn)入線程池祷杈,等待下一次獲取資源斑司;sleep不會(huì)釋放鎖,僅僅讓出CPU
(3) sleep的線程在時(shí)間結(jié)束之后但汞,只需獲得CPU時(shí)間片就會(huì)繼續(xù)執(zhí)行宿刮;而wait的線程,必須等待其他線程調(diào)用notify或是notifyAll才會(huì)離開線程池特占,在獲得時(shí)間片之后才能繼續(xù)執(zhí)行yield
yield()是Thread的靜態(tài)方法糙置,執(zhí)行之后表示該線程讓出CPU,但是讓出CPU不代表該線程就不運(yùn)行了是目,如果在下次的競爭中谤饭,該線程獲得CPU,將繼續(xù)執(zhí)行懊纳。
yield對比sleep
(1) 相同:都是Thread的靜態(tài)方法揉抵,執(zhí)行之后都是讓出CPU
(2) 不同:Thread讓出CPU之后,交由其他線程去競爭嗤疯,yield讓出CPU之后冤今,交由與自己相同或是更高優(yōu)先級的線程去競爭,自己有可能繼續(xù)獲取cpu并執(zhí)行守護(hù)線程deamon
守護(hù)線程是一種特殊的線程茂缚,在后臺為系統(tǒng)提供服務(wù)戏罢。與之對應(yīng)的是用戶線程屋谭,只有當(dāng)最后一個(gè)用戶線程退出時(shí),守護(hù)線程才會(huì)結(jié)束龟糕,JVM也才會(huì)終止運(yùn)行桐磁。
注意:
在deamon線程退出時(shí),并不會(huì)執(zhí)行finally代碼塊
在deamon線程退出時(shí)讲岁,并不會(huì)執(zhí)行finally代碼塊
在deamon線程退出時(shí)我擂,并不會(huì)執(zhí)行finally代碼塊
public class DeamonThreadDemo {
public static void main(String[] args) {
Thread deamonThread = new Thread(()->{
while (true) {
System.out.println("i am alive");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println("i am finally quit");
}
}
});
deamonThread.setDaemon(true);
deamonThread.start();
try {
Thread.sleep(800);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
demo中,設(shè)置了守護(hù)線程deamonThread缓艳,主線程sleep(800)校摩,會(huì)讓守護(hù)線程獲得一次執(zhí)行機(jī)會(huì),打印一次“i am alive”和“i am finally quit”阶淘,主線程sleep(500)之后衙吩,守護(hù)線程繼續(xù)執(zhí)行一次“i am alive”,接著主線程退出舶治,守護(hù)線程也跟著退出分井,并沒有執(zhí)行finally代碼塊。